01_java基础
一,概述
1.1 什么是程序、什么是程序开发
程序是为了解决生活中的实际问题使用计算机语言所编写的一系列的指令的集合,简单的说就是软件。
程序开发:制作软件
1.2 Java的历史
1995年由SUN开发的。
Java5是2004年发布,Java被Oracle收购
Java8是2014年发布
Java的分类:
- JavaME:小型版,用于嵌入式
- JavaSE:标准版
- JavaEE:企业版
1.3 Java语言的特点
- 面向对象的编程思想
- 跨平台性:跨操作系统,一次编写,到处运行
- 健壮性
- 安全性
二,环境搭建
2.1 JDK的下载
下载地址:https://www.oracle.com/java/technologies/javase-downloads.html
2.2 JDK的卸载
控制面板卸载即可
2.3JDK的安装
2.4 配置环境
JDK:Java Development Kit,Java开发工具
JRE:Java Runtime Enviroment,Java运行环境
JVM:Java Virtual Machine,Java虚拟机
注:JDK包含JRE、JRE包含JVM
问:为什么要配置环境变量?
答:因为要运行java程序,就必须先经历编译的步骤,编译使用的指令是javac,而javac这个指令在jdk安装目录的bin目录下,只有在该目录下才能运行这个指令;实际开发中,我们希望编译的指令在计算机的任何位置都可以使用,所以需要配置环境变量。
配置的步骤:
- 右键“此电脑”,点击“属性”,点击“高级系统设置”,在“高级”标签页中,点击“环境变量”
- 在“系统变量”中,点击“新建”,在“变量名”中输入
JAVA_HOME
,在“变量值”中输入jdk安装的根目录- 编辑“系统变量”的“Path”中,新建
%JAVA_HOME%\bin
和%JAVA_HOME%\jre\bin
注:win7操作系统,点击“Path”后,在“变量值”的最前面,添加
%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
三,第一个Java程序
3.1 编写代码
编写的Java代码的扩展名是
.java
public class Test01{ public static void main(String[] args){ System.out.println("五五开"); } }
3.2 编译代码
需要通过
javac
指令对java文件进行编译,编译后会生成对应的.class
(二进制字节码)文件步骤:
win + r 打开命令提示符,输入cmd,回车
输入:
cd java文件所在位置
来切换执行路径到java文件所在的位置,注:如果在切换路径时也切换了盘符,需要在切换后,再输入一次目标盘符
编译文件
javac 文件名.java
3.3 运行代码
使用
java
指令运行代码:java 文件名(类名)
注:
- 先编译后运行
- 源代码发生改变后,必须重新编译
3.4 注意事项
Java代码应该以
;
或者}
结尾Java中的所有括号必须成对出现
Java中的符号必须是英文的
任何一个程序都必须有一个主函数,主函数也叫程序入口,它必须被定义成如下格式:
public static void main(String[] args){}
一个Java文件中只能有一个类是被public修饰,并且被public修饰的类名必须和文件名一致
四,基础语法
4.1 缩进
每一行在编写时都应该先按一下 tab 键(制表符)
4.2 关键字
概念:在Java中已经预先定义好的具有特殊含义的单词
例如:public、class、static、void、byte、short、int、long、float、double、char、boolean、if、else、switch、case、break、default、do、while、for、continue、return、new、private、interface、abstract、final、try、catch、finally、extends、implements、native、synchronized
注:main不是关键字
4.3 标识符
概念:自定义的名字,类名、方法名、变量名都是标识符
规则:
- 由数字、字母、下划线、美元符组成
- 不能以数字开头
- 不能与关键字重名
- 标识符区分大小写
规范:
变量名和方法名:首字母小写,多个单词组成时,从第二个单词起,每个单词的首字母大写(小驼峰)
public void sleep(){} public void goHome(){} int age; int ageOfChild;
类名:每个单词的首字母大写(大驼峰)
常量:所有字母大写,多个单词组成时,单词之间用下划线连接
见明知意、不宜过长
4.4 注释
概念:对代码进行解释说明的文字
格式:
- 单行注释:// 注释内容
- 多行注释:/* 注释内容 */
- 文档注释:/** 注释内容 */
特点:不参与程序的运行
五,变量
概念:计算机内存中的一块存储空间,它是存储数据的基本单元
5.1 变量的定义
方式一:先定义后赋值
格式: 数据类型 变量名; 变量 = 值; 例如:定义一个变量用于记录年龄值 int age;// 定义了一个int类型的变量age age = 23;// 为age变量赋值为23
方式二:定义的同时赋值
格式: 数据类型 变量名 = 值; 例如:定义一个变量用于记录身高值 /** 身高 */ double height = 148.5;// 定义了一个double类型的变量height并赋值为148.5
方式三:同时定义多个变量并赋值
/** a是xxx、b是xxx、c是xxx */ int a = 3,b,c = 5; b = 4;
注:
- 变量在使用前必须先赋值
- 变量可以被多次赋值,赋值后的新值会替换旧值
- 变量不能重复定义
- 变量只能在定义它的代码块中使用,一旦出了定义它的代码块,变量就被释放了
六,数据类型
Java中的数据类型分为:
- 基本数据类型
- 引用数据类型
6.1 基本数据类型(四类八型)
6.1.1 整数类型
byte:字节型,1个字节,范围:-128~127
short:短整型,2个字节,范围:-32768~32767
int:整型,4个字节,默认整型
long:长整型,8个字节
注:int表示的数据如果超过了int范围,需要加上“L”或者“l”
6.1.2 浮点类型
float:单精度浮点类型,4个字节
double:双精度浮点类型,8个字节,默认浮点类型
注:对float进行小数赋值时,需要加上“F”或者“f”
6.1.3 字符类型
char:2个字节,表示单个字符,用一对
单引号
括起来注:
- 只能存储一个字符,可以存储一个汉字
- 不能存储空字符
6.1.4 布尔类型
boolean:只有两个值,分别是:true 和 false
注:
- 布尔值不能参与算术运算
- 布尔值只能参与关系运算、逻辑运算
6.2 引用数据类型
6.2.1 字符串类型
概念:
String
类代表字符串。Java 程序中的所有字符串字面值(如"abc"
)都是字符串。定义:
String 变量 = "值"; 例如:定义一个人的姓名叫张三 String name = "张三";
6.3 转义字符
如果在字符中单独输出一个单引号,或者在字符串中输出一个双引号会编译失败,我们需要对它们进行转义
转义字符:\
常见的转义行为:
\\ \' \" \t:一个制表符 \n:一个换行符
七,运算符
7.1 算术运算符
+:求和运算,如果参与运算的数据中有字符串,那么“+”就是拼接,并且拼接后的结果仍是一个字符串
-:求差
*:求乘积
/:求商,整数之间的除法可能存在精度损失
%:取模,求余数
注:
- a % b,如果 a < b,那么结果就是 a
- a % b,如果 a > b,那么结果是[0,b)
- a % b,结果是正负与 a 一致
++、–:
- ++:自增1
- –:自减1
注:自增自减的前置和后置
- 在单独运算时,前置和后置没有区别
- 在复合运算时,
- 前置:先执行自增(减),再执行其他操作
- 后置:先执行其他操作,再执行自增(减)
7.2 赋值运算符
1. = 2. += 3. -= 4. *= 5. /= 6. %=
例如:
a -= b 等价于 a = a - b
7.3 关系(比较)运算符
关系运算的运算结果一定都是布尔值
1.==:判断相等 2.!=:判断不等 3.> 4.< 5.>= 6.<=
7.4 逻辑运算符
逻辑运算的运算结果一定都是布尔值,参与逻辑运算的数据也必须是一个布尔值
1.&:与,并且,表达式两边只要出现了false,整个表达式的结果就是false 2.|:或,或者,表达式两边只要出现了true,整个表达式的结果就是true 3.!:非,取反,true取反的结果是false,false取反的结果是true 4.^:异或,表达式两边相同,结果是false;两边不同,结果是true
注:
- 多个布尔值参与逻辑仍遵循上述特点
- &&:如果表达式左边是false,那么整个表达式的结果就是false,表达式右边不再运算
- ||:如果表达式左边是true,那么整个表达式的结果就是true,表达式右边不再运算
7.5 三元(目)运算符
格式:
表达式1?表达式2:表达式3
逻辑:当表达式1的结果为true时,执行表达式2;反之,执行表达式3
注:
- 表达式1必须是一个条件表达式
- 表达式2和表达式3可以是一个值,也可以是一个表达式,不能是语句
7.6 位运算(了解)
位运算:
&、|、^、<<
位运算的步骤:
- 将参与运算的十进制数转换程对应的二进制数
- 将转换后的1视为true,0视为false,执行逻辑运算
- 将得到布尔值转换回0或者1
- 将二进制转换回十进制
<<:左移
格式:a << b
表示:a 乘以 2 的 b 次方
7.7 二进制、十进制
二进制 -> 十进制
十进制 -> 二进制
八,类型转换
问:不同数据类型是否可以一起运算?
答:可以的,但是需要遵循一些规则
8.1 自动类型转换
自动类型转换的规则:范围小的数据与范围大的数据运算时,结果会自动转换成范围大的数据类型
范围大小的规则:double > float > long > int > short > byte
问:为什么布尔值和字符值不在规则中?
答:布尔值是逻辑值不能与其他类型运算;字符值是遵循ASCII码表进行转换的。
问:float占用4个字节,long占8个字节,为什么long的范围比float的范围小?
答:不同数据类型占用的字节数和不同数据类型所能表示的数据范围是没有关系的,float能表示的数据范围要比long的更大。
char类型的转换:char类型的值与十进制数的转换是遵循ASCII码表
- 65 ~ 90:‘A’ ~ ‘Z’
- 97 ~ 122:‘a’ ~ ‘z’
- 48 ~ 57 :‘0’ ~ ‘9’
8.2 强制类型转换(向下转型)
格式:
范围小的数据类型 变量名 = (范围小的数据类型)范围大的数据类型的数据;
注:
byte和byte、short和short、byte和short在运算时,会自动转换成int类型
自增、自减自带强制类型转换
+=、-= 等赋值操作自带强制类型转换
int值和char值可以通过强制类型转换实现数据类型的变换,根据ASCII码表进行转换;
汉字与十进制的转换关系在Unicode码表(万国码),它是一张前128位与ASCII码表完全相同,包含了各个国家、地区文字的码表
02_流程控制
一,分支结构
关键字:if、else
1.1 单 if 结构
格式:
if(条件表达式){ 语句 }
逻辑:判断条件表达式的结果,结果为true,执行代码块中的语句;反之,不执行
1.2 标准的 if - else 结构
格式:
if(条件表达式){ 语句1 }else{ 语句2 }
逻辑:判断条件表达式的结果,结果为true,执行 if 代码块中的语句;反之,执行 else 代码块中的语句
1.3 多条件的 if-else 结构
格式:
if(条件表达式1){ 语句1 }else if(条件表达式2){ 语句2 }else if(条件表达式3){ 语句3 } ...
逻辑:依次判断每一个条件表达式,当有一个条件表达式的结果为true时,执行相应代码块,其他的条件表达式不再判断。
if(条件表达式1){ 语句1 }else if(条件表达式2){ 语句2 }else if(条件表达式3){ 语句3 } ... else{ 语句n }
逻辑:依次判断每一个条件表达式,当有一个条件表达式的结果为true时,执行相应代码块,其他的条件表达式不再判断,如果没有任何一个条件表达式的结果为true,就执行最后一个else代码块中的语句n
1.4 嵌套的 if-else 结构
double score = 50; if(score >= 0 && score <= 100){ if(score >= 90){ System.out.println("A"); }else if(score >= 80){ System.out.println("B"); }else if(score >= 70){ System.out.println("C"); }else if(score >= 60){ System.out.println("D"); }else{ System.out.println("E"); } }else{ System.out.println("成绩有误"); }
逻辑:只有外层的条件满足了,才会进入内层结构
注:
- 如果if或者else所对应的代码块中只有一条语句,那么大括号可以省略
- else 不能单独使用
二,选择结构
关键字:switch、case、break、default
格式:
switch(表达式){ case 值1: 语句1 break; case 值2: 语句2 break; case 值3: 语句3 break; ... default: 语句n break; }
逻辑:将表达式的结果与每一个case后的值进行比较,当某一个case后值与表达式的结果相同值,执行相应代码,如果没有任何一个case后的值与表达式的结果相同值,则执行default中的代码
注:
- 表达式结果的类型必须和case后值的类型一致
- 每一个case后的值必须是唯一的
- case和default的顺序是任意的
- default不是必需的
- 表达式结果的类型只能是:byte、short、int、char、String
break:当程序运行到break关键字时,会跳出当前的整个switch-case
删除break后,switch-case结构会具有穿透性
三,循环结构
循环必须具备的4个要素:
- 条件表达式
- 循环体
- 初始化的值
- 步进表达式
3.1 while
格式:
while(条件表达式){ 循环体 }
逻辑:判断条件表达式的结果,如果是true,执行循环体,再次判断条件,直到条件表达式的结果为false,结束循环。
3.2 do-while
格式:
do{ 循环体 }while(条件表达式);
逻辑:先执行一次循环体,再判断条件表达式,如果是true,执行循环体,再次判断条件,直到条件表达式的结果为false,结束循环。
3.3 for
格式:
for(1初始化;2条件表达式;3步进表达式){ 4循环体 }
执行流程:1243243243…2
3.4 break 和 continue
break:可以用在switch-case和循环 中,用于
跳出
整个结构continue:只能用在循环中,用于
跳过
当前这次循环,继续下一次循环
3.5 死循环
概念:死循环就是无限循环
格式:
while(true){ }
do{ }while(true);
for(;;){ }
3.6 循环嵌套
补,Random、Scanner
1. Random
概念:Random是系统提供了一个专门用于获取随机数的一个类,在这个类中提供了一些获取随机数的方法
使用步骤:
创建对象
Random r = new random();
调用方法
int num1 = r.nextInt();// 随机一个int范围内的数 int num2 = r.nextInt(10);// 随机一个[0,10)范围内的数 double num3 = r.nextDouble();// 随机一个[0.0,1.0)
注:随机 [a,b] 可以写成:
r.nextInt(b-a+1)+a
2. Scanner
概念:Scanner是系统提供的一个专门用于接收键盘输入的类
使用步骤:
创建对象
Scanner sc = new Scanner(System.in);
调用方法
int num1 = sc.nextInt();//接收键盘输入int值 double num2 = sc.nextDouble();//接收键盘输入double值 String str1 = sc.next();//接收键盘输入的字符串 String str2 = sc.nextLine();//接收键盘输入的字符串
注:
- 必须保证输入的类型与调用方法所能接收的类型一致,否则会发生InputMismatchException
- next():不能接收空白字符
- nextLine():可以接收空白字符
03_数组
一,概念和特点
概念:用于存储数据的容器
特点:
- 只能存储同一种数据类型(定义时明确了数组的数据类型后,该数组就只能存储这个类型的数据)
- 数组的长度是固定的(定义时明确了数组的长度后,在后续的操作中该数组的长度不能发生变化)
二,数组的定义和创建
格式:
数据类型[] 数组名 = new 数据类型[长度];
数据类型[] 数组名 = new 数据类型[]{值1,值2,值3,...};
数据类型[] 数组名 = {值1,值2,值3,...};// 简化形式
三,数组中元素的赋值和获取
3.0 下标
下标也称为索引、脚标,本质上就是元素在数组中的位置(序号),一般使用index
特点:下标从0开始,数组的下标范围 [0,长度-1]
3.1 获取
格式:
数组名[下标]
注:
- 下标的范围是 0 ~ 长度-1 ,如果超出了范围会发生 ArrayIndexOutOfBoundsException 数组下标越界异常
- 如果数组只定义了长度,没有赋予初始化,那么数组中的元素会有默认值,默认值与数组的类型有关
- 整型默认值是0
- 浮点型默认值是0.0
- 字符型默认值是空格(空字符)
- 布尔型默认值是false
- 引用类型默认值是null
3.2 赋值
格式:
数组名[下标] = 值;
3.3 注意事项
- 无论是赋值还是获取,数组的下标范围都是0 ~ 长度-1
- 数组长度的获取方式:数组名.length
- 直接打印数组名输出是数组在内存中的地址,如果输出的是char类型的数组名,输出的是数组中的值
- 数组的工具类Arrays中提供了toString()方法用于将数组转换程字符串
四,数组的遍历
4.1 普通循环的遍历
数组的遍历就是对数组进行循环查看数组中的每一个元素
String[] ns = {"李逵","张飞","关羽","刘姥姥","孙悟空","八戒"}; for(int i = 0;i <= ns.length-1;i++){ System.out.println(ns[i]); }
4.2 增强for循环遍历(foreach)
格式:
for(数据类型 变量名 : 容器){ 循环体 }
注:
- foreach只能遍历数组、集合
- 定义的变量名在每次循环时,都会自动接收数组中被遍历到的元素
- foreach中没有下标的概念
- foreach循环一般用于查看容器中的元素
五,数组的复制
方式一:
遍历原始数组中的每一个元素,将它们分别赋值到新数组中
方式二:
使用 System 类中提供的 arrayCopy(数组a,i,数组b,j,len)
逻辑:将a数组中从 i 开始复制len个元素到b数组中从 j 开始
方式三:
使用 Arrays 类中提供 copyOf(原始数组,newLength)
逻辑:创建一个长度为 newLength 的新数组,并将原始数组中的所有元素复制到新数组中
数组的地址:
数组是引用数据类型,只要是一个引用数据类型,在创建时就会在堆内存中开辟存储空间,每一个开辟的空间都有自己的地址。直接打印数组名会输出这个地址。
当数组的引用变量被重新赋值后,那么这个数组就指向了新的地址。
六,二维数组
6.1 概念
概念:二维数组是数组的数组,二维数组中的每一个元素都是一个一维数组
6.2 二维数组的创建
方式一:
数据类型[][] 数组名 = new 数据类型[m][n]; // 这个二维数组中有m个一维数组,每个一维数组的长度是n
方式二:
数据类型[][] 数组名 = new 数据类型[m][]; // 创建了一个长度为m的二维数组(这个二维数组中有m个一维数组)
注:
- 使用该方式创建的二维数组中的每一个一维数组需要单独创建,否则会发生 NullPointerException 空指针
- 这些单独创建的一维数组不能使用简化方式来创建
方式三:
数据类型[][] 数组名 = new 数据类型[][]{ {值1,值2,值3,..}, {值1,值2,值3,..}, ...}; // 简化形式 数据类型[][] 数组名 = { {值1,值2,值3,..}, {值1,值2,值3,..}, ...};
04_方法
一,方法的概念
概念:方法就是完成一个功能时所需要执行的语句的集合
好处:
- 提高了代码的复用性
- 隐藏了功能的实现细节
二,方法的组成和定义
public static void main (String[] args) {...} 范围(权限)修饰符 特殊的修饰符 返回类型 方法名 参数列表 方法体
三,方法的调用
调用本类中的方法:直接调用
方法名(参数);
调用其他类中的方法:创建对象调用
数据类型 对象名 = new 数据类型(); 对象名.方法名(参数);
注:
- 方法只有被调用了才会运行
- 当方法被调用后会进入栈内存,栈内存的特点是“先进后出”。方法执行完毕后,方法会从栈内存中释放。
- 方法定义的位置是在类中其他方法之外的任意位置
- 方法分为定义方法和调用方法,定义方法时,需要明确方法的返回类型、方法名、参数列表;调用方法时必须遵循定义方法时格式
四,方法的参数
4.1 形式参数
定义方法时的参数列表叫形式参数也叫形参
作用:限定了调用者在调用此方法时,必须根据形参的个数、类型、顺序进行数据传递
4.2 实际参数
调用方法的参数列表叫实际参数也叫实参
实参是真正参与运算的数据,实参可以是值,也可以是表达式,还可以是其他方法的返回值
注:实参和形参的传递过程存在自动类型转换
五,方法的返回类型
返回:当方法运行完毕后,会将运行结果返回给调用者
返回类型是void:
表示这个方法没有返回值,这个方法在运行完毕后,不会将任何结果返回给调用者;
返回类型是void的方法方法可以使用
return
,但是这个return
只是用于结束方法,不能返回任何结果返回类型不是void:
表示这个方法有返回值,这个方法在运行完毕后,会将结果返回给调用者;
这个方法必须有
return
关键字并且return
后必须跟上返回的结果。此时调用这个方法就可以看成在使用这种类型的值
注:返回类型存在自动类型转换
关键字
return
:程序遇到它会立即结束当前方法作用:
- 返回类型不为void的方法,必须有
return
,以及返回的结果,return
用于结束方法并返回结果- 返回类型为void的方法,可以有
return
,也可以没有return
。如果写了return
,此时return
只是用于结束方法不能返回结果
六,参数和返回类型是引用类型
public static void main(String[] args) { int n = 2; f1(n); System.out.println(n);//2 int[] arr = {1,2,3,4}; f2(arr); System.out.println(arr[0]);//3 } public static void f2(int arr[]){ arr[0] = 3; } public static void f1(int n){ n = 3; }
public static void main(String[] args) { int[] a = f(); System.out.println(a); } public static int[] f(){ int[] arr = {1,2,3,4}; System.out.println(arr); return arr; }
参数和返回类型的数据类型如果是引用数据类型,那么传递的是地址。
参数是基本类型的传递称为值传递,当方法运行完毕后,方法从栈中释放,方法中所定义的形参也会随着方法的释放而释放。
参数是引用数据类型称为引用传递,当方法运行完毕后,由于引用数据类型的地址存储在堆内存中,所以不会随着方法的结束而释放。
七,方法的可变参数
概念:定义方法时,可以将参数定义成
数据类型...变量名
,此时传入的实参个数可以是任意的。特点:
- 可变参数的本质是数组
- 可变参数只能出现在参数列表的末尾
八,方法的重载
概念:同一个类中,多个方法具有相同的方法名,不同的参数列表就形成了方法的重载。
简而言之:方法名相同,参数列表不同(个数、类型、顺序)
好处:简单,方法记忆
05_面向对象
一,面向对象的概念
面向对象和面向过程都是编程思想
区别:
- 面向过程:重点放在程序的功能是如何执行的,以执行者的角度来思考
- 面向对象:重点放在程序的功能可以找谁来完成,以指挥者的角度来思考,更加贴近实际生活
二,类和对象
类:数据类型,一类事物的集合
- 基本信息
- 行为
对象:根据类创建出来的一个实体(实例),创建对象的过程称为实例化,这个对象具备了这个类中所有的基本信息和行为
例如:研究护士这类事物
类:定义护士类
基本信息:姓名、性别、工资、科室
行为:护理、打针
对象:创建护士类的对象
基本信息:张三、男、8000、肛肠科
行为:如何护理、如何打针
代码中:基本信息 -> 属性、行为 -> 方法
三,成员和局部
成员:全局,定义在类中,方法外
- 成员变量
- 默认值:成员变量有默认值,默认值与数据类型有关,规则与数组的默认值相同;局部变量没有默认值
- 作用范围:成员变量的作用范围是整个类;局部变量的作用范围是定义它的方法
- 生命周期:局部变量在方法被调用后进入栈内存,方法执行完毕后,局部变量随着方法的出栈而被释放;成员变量随着对象的创建而出现,当对象释放后,成员变量才被释放。
- 当成员变量和局部变量重名时,根据“就近原则”进行调用
- 成员方法
局部:定义在方法中
四,类的定义和对象的创建
4.1 类的定义
格式:
public class 类名{ 属性 方法 }
public class Nurse { public String name; public int age; public boolean gender; public void daZhen(){ System.out.println("打针"); } public void huLi(){ System.out.println("护理"); } }
4.2 对象的创建
格式:
数据类型 对象名 = new 数据类型();
Nurse n1 = new Nurse();
五,属性和方法的调用
格式:
// 调用方法 对象名.方法名(); // 调用属性 // 1.赋值 对象名.属性名 = 值; // 2.获取 对象名.属性名
六,将引用数据类型作为方法的参数和返回值
如果传递的参数是引用数据类型,那么形参接收到的是实参的地址。
如果返回值是引用数据类型,那么接收这个返回结果的变量接收到的是返回的地址。
注:
引用数据类型的对象在使用前,必须先创建,否则会发生
NullPointerException
,空指针异常
06_封装
一,封装的概念
生活中的封装:机箱、打包盒
代码中的封装:包、类、方法
二,封装的好处
- 隐藏了实现的细节
- 提高了代码的安全性
- 提高了代码的复用性
三,封装的必要性
问题:
- 属性可以通过对象随意调用
- 对属性值没有进行合理性的判断
解决:
- 通过对属性私有化来限制属性的调用
- 对属性值进行合理性的判断
四,如何对属性进行封装
4.1 属性私有化
实现方式:使用关键字
private
来修饰属性
private
关键字是范围(权限)修饰符中的一个,表示私有的,被private
所修饰的成员只能在本类中使用,对其他类来说是不可见的。
4.2 对外提供这个私有属性的访问方法
赋值:
public void setAge(int age){ if(age < 0 || age > 120){ return; } this.age = age; }
获取:
public int getAge(){ return this.age; }
set 方法:public、无返回、setXxx、要有参数、this.xxx = xxx
get 方法:public、有返回、getXxx、无参、return
07_构造函数、包和导入、范围修饰符、静态
一,构造函数(方法、器)
1.1 构造函数的特点
- 构造方法的方法名与类名一致
- 每一个类都有一个默认的隐式的无参的构造函数
- 构造函数没有返回类型,连void都不能有
- 每次创建对象时都会执行构造函数
- 一旦构造函数重载了,那么原来的默认构造函数就不存在了,不能再使用它,除非把它写出。
1.2 构造函数的作用
- 创建对象
- 创建对象的同时对属性进行初始化
1.3 构造函数的重载
定义若干个构造函数,参数列表不同
作用:创建对象的同时对属性进行初始化
public Emp(String id, String name, double salary) { this.id = id; this.name = name; this.salary = salary; }
Emp e = new Emp("10086","zs",66666);
标准的JavaBean:私有化的属性、对外提供的set/get方法、无参构造函数
1.4 this 关键字
在创建对象后,可以通过 this 关键字来获取该对象的地址。可以通过 this 来表示对象(调用者),因此可以通过 this 对类中的成员进行调用
this 的作用:
调用本类的属性、调用本类的方法
调用本类中的其他构造函数
public Student(){ // this(""); System.out.println("默认构造函数"); } public Student(String name){ this(); this.name = name; System.out.println("重载构造函数"); }
作用:当多个构造函数中出现了重复的初始化语句时,可以使用 this 调用其他构造函数来简化对属性的初始化动作。
注:构造函数的调用只能出现在其他构造函数的第一行
二,包和导入
2.1 包
包:关键字 package,本质就是文件夹
问:为什么要创建不同的包?
答:因为一个项目中会涉及到很多模块、会涉及到很多相关的技术,创建不同的包,就在创建多个不同的文件夹,当我们将相关的类放入对应的文件夹(包)时,类的管理会更加方法。
注:
每一个类都应该在一个包中,并且这个类的第一行必须明确这个类的包路径(也就是明确这个类是属于哪个包的)
package com.qf.test; public class Emp { }
一个类的完整路径(类全名、全类名)
com.qf.test.Random
2.2 包的命名
规则:标识符的规则
规范:全小写,用
.
来划分包的层次,一般使用域名倒着写常见的包名:
- 实体:entity、domain、pojo、bean、vo
- 工具:util
- 测试:test
- 数据库:dao (data access object)
- 业务逻辑:service、business
- 控制层:controller
2.3 导入
导包:当一个类中使用到了其他包中的类时,需要将这个类引入进来
注:同包中的类互相调用时不需要导包。
导入的关键字:import
import 完整包名.类名; // import java.util.Random; // import java.io.File;
按需导入:
import java.util.*; /* 可以代替 import java.util.ArrayList; import java.util.Calendar; import java.util.Scanner; */
注:java.lang (语言包)中的类不需要手动导入,会自动导入
三,范围(权限)修饰符
- public:公共的,公开的
- protected:受保护的
- [default]:默认的,不需要定义任何范围修饰符
- private:私有的
注:范围修饰符只能修饰成员
public protected [default] private 本类中 √ √ √ √ 同包不同类 √ √ √ × 不同包的子类 √ √ × × 不同包的其他类 √ × × ×
四,静态
静态的关键字:static
4.1 静态的特点
静态成员随着类的加载而加载
静态成员,不属于对象,属于整个类,是这个所有对象的共享内容
静态成员可以使用类名调用
静态成员只能访问静态的,不能访问非静态的;非静态的成员既可以访问静态的,也可以访问非静态的
注:后人可以访问前人,前人无法访问后人
静态不能访问this
静态成员只能修饰成员
4.2 静态代码块
格式:
public class 类名{ static { // 静态代码块中的语句 } }
特点:
- 静态代码块最优先执行
- 静态代码块只执行一次
作用:
- 读取配置文件
- 对静态属性进行初始化
注:多态静态代码块会根据书写的先后顺序依次执行
4.3 静态常量
格式:
public static final 数据类型 变量名 = 值;
08_继承、抽象类、接口、多态
一,继承
1.1 继承的概念
生活中的继承:子女拥有父母的东西
代码中的继承:指的是类与类之间产生了关系,多个类中的共性内容向上抽取
父类:超类、基类
父类的范围往往比较大,但是属性和方法一般都比较少
子类:派生类、衍生类
子类的范围往往更加精细,属性、方法一般比较多
所有类都直接或间接的继承了
Object
类注:子类的命名一般使用父类的名字作为后缀
1.2 继承的好处
- 子类可以直接访问父类中非私有的成员,提高了代码的复用性
- 为多态提供了前提
1.3 子类父类的继承关系
关键字:extends
// 父类 public class Animal { public int age; public String name; public void eat(){ System.out.println(name+"在吃"); } } // 子类 public class Dog extends Animal{ }
注:
- 多个类可以继承同一个父类(一个父类可以有多个子类)
- 一个类不能同时继承多个父类
- 继承具有传递性
- 子类除了具有父类的共性内容之外,还可以有特有内容
1.4 子类父类中同名成员的问题
1.4.1 同名变量
当子父类中出现同名变量时,会根据“就近原则”,优先访问子类中的成员
如果要在子类中访问父类的同名变量,要使用关键字
super
来访问public class Fu { int i = 3; } public class Zi extends Fu{ int i = 6; public void f(){ System.out.println(super.i); } }
1.4.2 同名方法(override:方法的重写、覆盖、复写)
重写的概念:在继承关系中,子类对父类的功能进行扩展就是方法的重写
重写的格式:
- 方法名相同
- 方法的参数列表相同
- 子类重写方法的范围修饰符要大于等于父类的范围修饰符
- 返回类型是引用数据类型时,子类重写方法的返回类型必须是父类方法返回的类型或者是该类型的子类
注:
- 当子父类中出现同名方法时,会根据“就近原则”,优先访问子类中的成员
- 可以使用
@Override
来校验当前的方法是否是重写- 即使子类中重写了父类的方法,对父类的功能进行了扩展,也不能删除父类的原有功能
- 子类中可以通过
super
来访问父类中的同名方法
1.5 父类属性私有化的问题
问题:当父类属性私有化后,对子类就不可见了,子类无法访问到这些私有属性。
解决:
方式一:父类中的属性私有化了,但是对外提供的set/get方法是可以访问的。所以子类可以通过父类的set/get访问来访问父类的私有属性
方式二:如果子类想在创建对象的同时对这些私有化的属性进行初始化,可以在子类的重载构造函数中调用父类的重载构造函数
// 父类 public class Animal { private String name; private int age; public Animal(){ } public Animal(String name, int age) { this.name = name; this.age = age; } } // 子类 public class Dog extends Animal{ public Dog(){ } public Dog(String name,int age){ super(name,age); } }
1.6 继承关系中的构造函数
- 每一个类的每一个构造函数中都调用了父类的默认构造函数
- 父类构造函数的调用必须在子类构造函数的第一行
1.7 super 关键字
作用:
- 调用父类的属性、调用父类的方法
- 调用父类的构造函数
- 每一个类的每一个构造函数中的第一行都调用了父类的默认构造函数
- 子类的重载构造函数中需要调用父类的重载构造函数来对属性进行初始化
1.8 final 关键字
final 可以修饰:
变量/对象:创建后都不能再被赋值
注:成员常量在创建时必须手动初始化
类:变成最终类,不能被继承
方法:不能被重写
1.9 对子类特有属性的初始化
// 父类 public class Emp { private String name; private double salary; public Emp() { } public Emp(String name, double salary) { this.name = name; this.salary = salary; } } // 子类 public class Manager extends Emp{ // 特有属性 private double bonus; public Manager() { } public Manager(String name, double salary,double bonus) { // 共性属性的初始化 super(name, salary); // 特有属性的初始化 this.bonus = bonus; } }
二,抽象类
2.1 抽象方法和抽象类的概念
概念:当子类的共性方法抽取到父类中,而父类无法描述每个子类的具体实现时,就应该将这些方法定义成抽象方法,抽象方法所在的类就是抽象类。
关键字:abstract
抽象方法和抽象类的定义格式:
public abstract class Shape { public abstract double getC(); public abstract double getS(); } // 抽象方法没有方法体,用abstract关键字来修饰,抽象方法所在的类是抽象类,也用abstract关键字来修饰
2.2 抽象类和抽象方法的使用
抽象类的使用:
创建子类继承抽象类
重写所有抽象方法
注:如果一个类继承了抽象类,要么重写它的所有抽象方法、要不这个子类也是一个抽象类
创建子类对象调用方法
2.3 注意事项
不能与abstract关键字一同使用的关键字:private、static、final
抽象类不能被实例化(不能创建抽象类的对象,不能new)
抽象类一定是一个父类,因为抽象类是向上抽取形成的
抽象方法一定在抽象类中
抽象类中可以有非抽象方法
注:父类中的共性方法可以描述各个子类的具体实现时,就没有必要抽象了
抽象类中有构造函数
2.4 匿名对象
概念:创建对象时不指定对象名
new Random().nextInt(10);
好处:书写简便
弊端:
- 只能使用一次
- 可读性差
new FileWriter(new File("C:\\Users\\86151\\Desktop\\2009\\test.txt")).write("你好呀!!!");
2.5 匿名内部类
概念:使用匿名的方法创建一个抽象类的子类对象
格式:
new 抽象类(){ // 重写所有抽象方法 }; new Emp(){ public void work(){ System.out.println("在开发"); } }.work();
注:
- 匿名内部类没有类名,匿名内部类没有构造函数
- 普通类也可以使用匿名内部类
- 匿名内部类创建出的对象可以通过多态使用父类的引用变量来接收子类对象(后面讲)
三,接口
3.1 接口的概念
概念:接口就是多个类的公共规则,接口是一种引用数据类型,它本质上是一个类,接口是一种特殊的类
接口的作用:
- 提供了标准、规范
- 扩展了功能
3.2 接口的定义
关键字:interface
格式:
public interface 接口名{ }
接口在编译后生成的仍然是
.class
的二进制字节码文件
3.2.1 接口中的成员变量
接口中的成员变量默认被
public static final
修饰
3.2.2 接口中的成员方法
接口中的成员方法默认被
public abstract
修饰
3.3 接口的使用
关键字:implements
步骤:
- 创建实现类实现接口
- 重写所有抽象方法
- 创建实现类对象,调用方法
接口也可以使用匿名内部类的方式来创建实现类对象
new 接口(){ // 重写所有抽象方法 }; new Usb(){ @Override public void f() { } }.f();
四,接口的新特性
4.1 接口中的普通(默认)方法
从Java8开始,接口中允许定义普通(默认)方法
格式:
public default 返回类型 方法名(参数列表){ 方法体 }
意义:为了解决接口中功能升级的问题
注:接口中的普通(默认)方法也可以重写,重写后,不需要加上default,因为default关键字只能接口中定义普通(默认)方法时使用
4.2 接口中的静态方法
从Java8开始,接口中允许定义静态方法
格式:
public static 返回类型 方法名(参数列表){ 方法体 }
注:
- 接口中静态方法的定义与普通类中的定义方式相同
- 接口中的静态方法只能通过接口名直接调用,不能通过实现类的对象或者实现类的类名调用
4.3 接口中的私有方法
从Java9开始,接口中允许定义私有方法
格式:
private 返回类型 方法名(参数列表){ 方法体 }
作用:不需要对外暴露,目的是为接口中的普通(默认)方法提供功能上的支持
4.4 类与接口的关系
4.4.1 类与类的关系
类与类之间的关系是继承:子类继承父类
格式:
public class 子类 extends 父类{ }
注:类与类之间只存在单继承
4.4.2 类与接口的关系
类与接口之间的关系是实现:实现类实现接口
格式:
public class 实现类 implements 接口A, 接口B{ }
注:
- 一个类可以同时实现多个接口
- 实现类实现多个接口后必须重写所有接口中的抽象方法
- 多个接口中如果出现了同名的抽象方法,实现类只需要实现一次
- 如果多个接口中出现了同名的default方法,那么实现类必须进行重写,此时如果要在实现类重写的方法中调用父接口中的同名方法需要:
接口名.super.方法名()
- 如果一个接口中的抽象方法与另一个接口中的default方法同名,那么实现类必须进行重写
4.4.3 接口与接口的关系
接口与接口之间的关系是继承:子接口继承父接口
格式:
interface B{ } interface C{ } interface A extends B, C{ }
注:
- 一个父接口中的default方法与另一个父接口中的抽象方法同名,子接口必须重写
- 多个父接口中有同名的default方法,子接口必须重写
4.4.4 类在继承类的同时实现接口
格式:
class A{ } interface B{ } interface C{ } class D extends A implements B, C{ }
注:
- 父类中与接口中出现了同名方法时,子类默认调用父类的;要调用父接口中的同名方法需要:
接口名.super.方法名()
4.5 接口和抽象类的异同
同:
- 抽象类和接口都不能创建对象
- 抽象类和接口中都可以定义抽象方法
- 抽象类和接口都需要被继承或者实现,通过子类或者实现类重写抽象方法
异:
- 抽象类被子类继承(extends),接口被实现类实现(implements)
- 类与类之间只能有单继承,接口与接口之间可以有多继承
- 抽象类中的成员变量的定义是可以任意的,接口中的变量都是静态常量
- 接口中的方法都是public的
- 接口中的普通方法需要default修饰
- 抽象类中有构造函数,接口中没有构造函数
- 抽象类封装的是子类的共性内容,接口是对功能的封装,接口是方法的集合
- 接口的抽象级别比抽象类更高
五,多态
5.1 多态的概念
多态的前提是继承或者实现
程序中的多态:“一个子类就是一个父类”
代码中的体现:父类的引用变量指向子类对象(把子类对象赋值给父类的引用变量)
代码:
public class Emp { } public class Coder extends Emp{ } // 多态 // 父类/接口 对象名 = new 子类()/实现类(); Emp e = new Coder();
5.2 多态下调用成员的特点
5.2.1 成员变量的特点
编译时期:父类中没有这个变量,编译失败;父类中有这个变量,编译通过
运行时期:访问的是父类中的变量
小结:编译运行都看左边
注:多态下,不能访问子类的特有变量
5.2.2 成员方法的特点
编译时期:父类中没有这个方法,编译失败;父类中有这个方法,编译通过
运行时期:访问的是子类重写的方法
小结:编译看左边,运行看右边
注:多态下,不能访问子类的特有方法
5.3 多态的应用
场景1:使用父类作为方法的形参,此时方法的实参可以是该父类的任何一个子类对象,使方法的实参更加灵活
场景2:使用父类作为方法的返回类型,此时方法的返回值可以是该父类的任何一个子类对象,使方法的返回更加灵活
09_内部类、代码块
一,内部类
1.1 概念
概念:将一个类定义在另一个类的内部
例如:人体和器官、汽车和发动机
注:内部类在编译时也会生成 .class 文件
1.2 分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
1.3 成员内部类
成员内部类编译后的文件名是:外部类$内部类.class
格式:
public class 外部类{ // 外部类的其他成员 范围修饰符 class 内部类{ // 内部类中的成员 } }
访问规则:
内部类访问外部类
- 内部类可以直接访问外部类中的成员,包括私有的
- 内部类和外部类中出现同名成员时,
- 在内部类中创建外部类的对象进行调用
- 外部类.this.成员
外部类访问内部类:在外部类中创建内部类的对象进行调用
其他类访问内部类
通过外部类间接访问:在外部类的方法中,创建内部类对象,访问内部类的成员,再在其他类中通过外部类的对象访问
直接创建内部类对象:
// 外部类.内部类 对象名 = new 外部类().new 内部类(); Outer.Inner in = new Outer().new Inner();
1.4 静态内部类
静态内部类编译后的文件名是:外部类$内部类.class
格式:
public class 外部类{ // 外部类的其他成员 范围修饰符 static class 内部类{ // 内部类中的成员 } }
访问规则:
静态内部类访问外部类:
- 可以直接访问外部类中的静态成员,如果内部类和外部中类出现了同名成员,需要通过外部类名调用外部类的成员
- 访问外部类中的非静态成员,必须创建外部类的对象进行调用
外部类访问静态内部类:必须在外部类中创建内部类的对象进行调用
其他类访问静态内部类:
静态内部类中的成员是静态的:外部类.内部类.成员
静态内部类中的成员是非静态的,需要创建对象进行调用
// 外部类.内部类 对象名 = new 外部类().new 内部类(); Outer.Inner in = new Outer().new Inner();
注:可以通过
import xxx.xxx.xxx.外部类.内部类
简化静态内部类的调用
内部类中成员的特点:
如果内部类中的成员是静态的,那么这个内部类必须是静态内部类
1.5 局部内部类
局部内部类编译后的文件名是:外部类$序号内部类.class
格式:
public class 外部类{ 范围修饰符 返回类型 方法名(参数列表){ class 内部类{ // 内部类中的成员 } } }
访问规则:
- 局部内部类访问外部类:
- 内部类可以直接访问外部类的成员,包括私有的
- 内部类和外部类中出现同名成员时,
- 在内部类中创建外部类的对象进行调用
- 外部类.this.成员
- 外部类访问局部内部类:只能在外部类的访问中调用定义局部内部类的方法,并且需要在定义局部内部类的语句后创建该内部类的对象才能访问它的成员
- 其他类访问局部内部类:只能创建其他类的对象,调用调用了定义局部内部类的方法
1.6 匿名内部类
作用:通过匿名的方式创建抽象类的子类对象或者接口的实现类对象
格式:
abstract class A{ public abstract void f(); } interface B String t(int num); } // 创建抽象类A的子类对象 new A(){ public void f(){ } }; // 创建接口B的实现类对象 new B(){ public String t(int num){ return num+"哈哈哈"; } };
二,代码块
2.1 概念
概念:使用一对大括号括起来的一段代码就是一个代码块
2.2 分类
- 普通代码块
- 静态代码块
- 构造代码块
- 同步代码块(线程)
2.3 普通代码块
写在局部位置的代码块
注:变量只能在定义它的代码块中使用,代码块结束了,代码块中定义的变量也就被释放了
2.4 静态代码块
概念:在成员位置上,使用static修饰的代码块
格式:
static{ // 静态代码块中的语句 }
特点:
- 静态代码块最优先执行
- 静态代码块只执行一次
作用:
- 读取配置文件
- 对静态属性进行初始化
注:多态静态代码块会根据书写的先后顺序依次执行
2.5 构造代码块
概念:定义在成员位置上,没有任何修饰符的代码块
格式:
public class 类名{ { // 构造代码块中代码 } }
特点:
- 每次创建对象都会执行
- 优先于构造函数执行,晚于静态代码块
作用:为对象的那些不是基本信息的属性进行初始化
10_常用类
一,Scanner
概念:Scanner是一个基本类型和字符串的简单文本扫描器,它在 java.util 包中
构造函数:
Scanner sc = new Scanner(System.in);
常用方法:
int nextInt()
int nextInt(int radix)
例如:nextInt(2):表示系统会将输入的内容视为二进制数,转换成十进制数后返回
double nextDouble()
String next():接收字符串,该方法会将空白字符视为输入了结束符号,因此该方法不能接收空白字符
String nextLine():接收字符串,可以接收空白字符
注:如果输入数据的类型与指定类型不匹配会发生 InputMismatchException,输入类型不匹配
二,Random
概念:此类的实例用于生成伪随机数流,它在 java.util 包中
构造函数:
Random r = new Random();
常用方法:
int nextInt()
int nextInt(int bounds):随机[ 0,bounds-1 ]
公式:随机[ n,m ]之间的数,nextInt(m - n + 1) + n
double nextDouble():随机 [ 0.0,1.0 )
三,Math
概念:Math 类包含用于执行基本数学运算的方法,它在 java.lang 包中
注:
- Math 类是 final 的
- Math 类中的成员都是静态
- Math 类的构造函数的私有化的
静态常量:
- PI:π
- E:自然对数的底数
常用方法:
abs(double n):求绝对值
ceil(double n):向上取整
floor(double n):向下取整
toDegrees(double n):弧度制转角度值
toRadians(double n):角度值转弧度制
sin(double n)
pow(double a,double b):求a的b次方
max(double a,double b)
min(double a,double b)
sqrt(double n):开根号
random():随机返回 [ 0.0,1.0 )
rint(double n):四舍五入,返回 double
round(double n):四舍五入,返回 int
注:rint(-3.5) 返回-4.0;round(-3.5)返回 -3
四,String
概念:
String
类代表字符串。Java 程序中的所有字符串字面值(如"abc"
)都作为此类的实例实现。 字符串是常量;它们的值在创建之后不能更改。因为 String 对象是不可变的,所以可以共享。注:
- 所有字符串字面值都是String类的对象
- 字符串都是常量
- 字符串存储在字符串常量池中,是共享的
- == 在比较基本数据类型时,比较的是数据值;== 在比较引用数据类型时,比较的是对象的地址值;比较字符串中的内容是否相同应该是使用
equals()
构造函数:
- new String():创建一个空的字符串
- new String(byte[] bytes):将字节数组中的元素根据ASCII码表转换成字符后生成字符串
- new String(char[] chars):将字符数组中的元素拼接成一个字符串
- new String(String s)
- new String(byte[] bytes, Charset charset):可以根据指定的字符集编码生成字节数组所对应的字符串
- new String(byte[] bytes , int offset , int length):从字节数组的指定位置开始获取指定数量的元素拼接成字符串
- new String(char[] chars , int offset , int count):从字符数组的指定位置开始获取指定数量的元素拼接成字符串
常用方法
- charAt(int index):根据下标返回字符
- indexOf(String str):根据字符串内容返回所在下标
- indexOf(String str,int index):从指定位置开始查找指定字符串所在的下标
- lastIndeOf(String str):返回最后一个字符串内容所在的下标
- lastIndexOf(String str, int index):从指定位置开始,从后往前查找最后一个字符串所在的下标
- compareTo(String str):根据字典顺序比较字符的大小,返回差值
- compareToIgnoreCase(String str):忽略大小写比较
- equals(String str):比较字符串的内容,返回布尔值
- equalsIgnoreCase(String str):忽略大小写比较字符串内容
- contains(String str):判断字符串是否包含子串
- codePointAt(int index):通过下标返回字符的十进制数
- concat(String str):拼接字符串
- endsWith(String str):判断是否以指定字符串结尾
- startsWith(String str):判断是否以指定字符串开头
- getBytes():获取字符串的字节数组
- toCharArray():获取字符串的字符数组
- isEmpty():判断字符串的内容是否为空
- length():返回字符串的长度
- replace(String oldStr, String newStr):将新字符串替换原始字符串
- substring(int index):从指定位置截取到字符串末尾
- subString(int beginIndex, int endIndex):从begin下标截取到end下标,不包含end
- toLowerCase():将大写字母转换成小写字母
- toUpperCase():将小写字母转换成大写字母
- trim():去除字符串两端的空格
正则表达式:
概念:正则表达式是一种用来对其他字符串进行匹配、切割、替换、查找的规则,它本身也是一个字符串
功能:
- 匹配:字符串.matches(正则)
public static void f1(String email){ String regex = "\\w{6,16}@[0-9a-zA-Z]{2,8}[.]com"; System.out.println(email.matches(regex)); }
- 切割:字符串.split(正则)
public static void f2(){ String ip = "192-+-+-+-+-168++++1+++1"; String[] strs = ip.split("[+-]+"); System.out.println(Arrays.toString(strs)); }
- 替换:字符串.replaceAll(正则,新值)
public static void f3(){ String str = "你骑过马马马马吗?我骑过马马马马马呀!"; String regex = "马+"; str = str.replaceAll(regex,"*"); System.out.println(str); } // 叠词替换 public static void f4(){ String str = "我我我我我我爱爱爱爱爱爱学习习习习习习习习习习习习"; // 我爱学习 String regex = "(.)\\1+"; str = str.replaceAll(regex,"$1"); System.out.println(str); }
- 查找
public static void f(){ String msg = "today is a funny day,let us go out and play"; Pattern p = Pattern.compile("\\b[a-z]{5}\\b"); Matcher m = p.matcher(msg); while(m.find()){ System.out.println(m.group()); } }
五,StringBuilder 和 StringBuffer
为什么要使用StringBuilder 和 StringBuffer?
原因:
- String 是不可变,StringBuilder 和 StringBuffer 是字符串缓冲区,是可变的
- String 资源占用大
构造函数:
- StringBuilder()
- StringBuilder(int capacity)
- StringBuilder(String str)
常用方法:
- append(Object obj)
- delete(int start, int end)
- deleteCharAt(int index)
- insert(int index, Object obj)
- reverse()
- setCharAt(int index, Char c)
StringBuilder 和 StringBuffer 的区别:
- StringBuffer 保证线程安全的(同步的)
- StringBuilder 速度快,不保证同步
StringBuilder、StringBuffer 与 String 的转换
- StringBuilder、StringBuffer -> String
// 使用toString() StringBuilder sb1 = new StringBuilder("abc"); String s1 = sb1.toString(); // 使用String的构造函数 StringBuffer sb2 = new StringBuffer("abc"); String s2 = new String(sb2);
- String -> StringBuilder、StringBuffer
// 使用StringBuilder、StringBuffer的构造函数 String s3 = "abc"; StringBuilder sb3 = new StringBuilder(s3); StringBuffer sb4 = new StringBuffer(s3);
六,Date
概念:类
Date
表示特定的瞬间,精确到毫秒。作用:
- 通过Date对象,获取对应的年、月、日、小时、分钟和秒值
- 将Date对象格式化成指定格式的日期字符串;将指定格式的日期字符串转换成Date对象
构造函数:
Date d1 = new Date(); System.out.println(d1);// Thu Jan 14 17:24:28 CST 2021 Date d2 = new Date((long)(System.currentTimeMillis() + 365.0 * 24 * 3600 * 1000)); System.out.println(d2); Date d3 = new Date(82,9,19,17,0,0); System.out.println(d3);
常用方法:
- getXxx():获取指定的时间字段
- setXxx():设置指定的时间字段
- a . after (Date b):判断日期 a 是否在日期 b 之后
- a . before (Date b):判断日期 a 是否在日期 b 之前
- getTime():功能同 System.currentTimeMillis();
七,DateFormat
概念:DateFormat 是一个抽象类,系统提供了它的一个子类SimpleDateFormat。
SimpleDateFormat允许进行格式化(也就是日期 -> 文本)、解析(文本-> 日期)
功能:
- 格式化(Date对象 -> String)
Date d = new Date(); System.out.println("格式化前:"+d); // 2021-01-14 17:52:03 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String time = sdf.format(d); System.out.println("格式化后:"+time);
- 解析(String -> Date对象)
String time = "2038-01-14 175720"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HHmmss"); Date d = sdf.parse(time); System.out.println(d);
注:如果要解析的日期字符串的格式与指定格式不匹配会发生ParseException解析异常
八,Calendar
概念:
Calendar
类是一个抽象类,它为特定瞬间与一组诸如YEAR
、MONTH
、DAY_OF_MONTH
、HOUR
等日历字段
之间的转换提供了一些方法。
Calendar
提供了一个类方法getInstance
,以获得此类型的一个通用的对象。Calendar
的getInstance
方法返回一个Calendar
对象,其日历字段已由当前日期和时间初始化Calendar rightNow = Calendar.getInstance();
常用方法:
get(int field):返回指定日历字段的值
set(int field,int value):为指定的日历字段设置值
set(int year,int month,int date,int hour,int minute,int second)
add(int field,int value):为指定的日历字段设置偏移值
value:
正数:添加
负数:减少
getTime():返回一个Date对象
getTimeInMillis():功能同 System.currentTimeMillis();
九,Object
概念:类
Object
是类层次结构的根类。每个类都使用Object
作为超类。常用方法:
toString()
打印对象时,会默认调用toString()方法,如果没有重写Object中的toString(),会执行Object中的toString(),输出地址:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
我们可以对Object中的toString()进行重写,来更方便直观的查看对象的属性。
@Override public String toString() { return "Stu{" + "name='" + name + '\'' + ", age=" + age + '}'; }
equals()
重写equals()可以比较两个对象的属性是否完全相同
@Override public boolean equals(Object obj) { if(this == obj){ return true; } if(obj == null){ return false; } if(! (obj instanceof Stu)){ return false; } Stu s = (Stu)obj; return this.name.equals(s.name) && this.age == s.age; }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; Stu stu = (Stu) o; return age == stu.age && Objects.equals(name, stu.name); } // Objects类的静态方法equals() public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
十,Arrays
概念:Arrays是一个专门用于操作数组的工具类
常用方法:
toString(数组)
binarySearch(数组,value):使用二分搜索法,在数组中查找元素所在下标,
前提:1. 升序排列 2.不能有重复元素
sort(数组)
copyOf(数组,长度)
fill(数组,value)
asList(T…a):根据指定的可变参数返回一个由这些参数组成的ArrayList集合
使用 sort() 方法根据对象属性进行排序:
方式一:调用sort(数组)
实现Comparable接口,并指明泛型
重写compareTo()方法
根据要排序的内容和升降序的顺序,返回属性的差值
升序:this.属性 - o.属性
降序:o.属性 - this.属性
// 年龄降序,年龄相同时以成绩升序 @Override public int compareTo(Stu o) { return o.age == this.age ? this.score - o.score : o.age - this.age ; }
方式二:调用sort(数组,Comparator接口)
Arrays.sort(stus,new Comparator<Stu>(){ public int compare(Stu s1,Stu s2){ return s2.age - s1.age;// 年龄降序 } });
十一,包装类
11.1 概念
概念:基本数据类型没有对象,不能调用属性和方法。如果要使用基本数据类型调用属性和方法,就需要使用这些基本数据类型所对应的引用数据类型,这种引用数据类型就称为包装类。
基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean
11.2 包装类对象的创建
格式:
- Integer i = new Integer(int i);
- Integer j = new Integer(String s);
注:如果参数不是指定的类型会发生 NumberFormatException 数字格式异常
11.3 成员的变量和成员方法
- static MIN_VALUE
- static MAX_VALUE
- static max(int a,int b)
- static min(int a,int b)
- static compare(int a,int b):返回0,表示 a == b;返回1,表示a > b;返回-1,a < b
- static toBinaryString(int i)
- static toOctalString(int i)
- static toHexString(int i)
11.4 装箱和拆箱
概念:基本数据类型和对应包装类的转换就是装箱和拆箱
装箱:基本类型 -> 包装类
构造函数
int i = 3; Integer j = new Integer(i);
静态方法
int m = 4; Integer n = Integer.valueOf(m);
拆箱:包装类 -> 基本类型
成员方法
Integer x = new Integer(3); int y = x.intValue();
从 jdk 1.5 开始提供了自动装拆箱(直接赋值)
Integer p = 666; int q = p;
11.5 基本类型(包装类)与字符串类型的转换
基本类型(包装类)-> 字符串
- 拼接空字符串
- 使用包装类的静态方法toString(参数)
- 使用包装类的成员方法toString()
- 使用String类的静态方法valueOf(参数)
字符串 -> 基本类型(包装类)
- 使用包装类的构造函数(不推荐)
- 使用包装类的静态方法parseXxx(String str)
- 使用包装类的静态方法valueOf(String str)
注:如果参数不是指定的类型会发生 NumberFormatException 数字格式异常
十二,System
概念:
System
类包含一些有用的类字段和方法。它不能被实例化。字段:
- in
Scanner sc = new Scanner(System.in); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); br.read(); br.readLine();
out
System.out.println();
err:“标准”错误输出流
System.err.println();
常用方法:
- System.currentTimeMillis();
- System.arrayCopy(Object src,int srcPos,Object des,int desPos,int length)
- System.exit(0):退出JVM,参数:0 表示正常退出,非0 表示异常终止
- System.gc():运行垃圾回收器
11_异常
一,异常
1.1 概念
概念:在程序中发生的不正常的情况,系统将其封装成了类,也就异常类,当运行到有异常的代码时,程序会终止。
1.2 异常的体系
Throwable:它是 Java 语言中所有错误或异常的超类
– Error:合理的应用程序不应该试图捕获的严重问题
– Exception:合理的应用程序应该试图处理的问题
1.3 异常的处理
1.3.1 使用 throws 抛出异常
作用:当方法内部发生异常后,可以通过
throws
将异常抛给调用者,让调用者来决定是进行捕获还是继续抛出,如果在不断抛出的过程中,有了捕获动作,那么异常就由捕获进行了处理。问:使用抛出的方式将异常抛给了调用,并没有实质上对异常进行处理,那它的意义是什么?
答:在方法的声明上告知了调用者当前方法会发生何种异常,那么不同的调用者就可以根据不同的具体情况对异常进行不同的处理方式
格式:
范围修饰符 返回类型 方法名(参数列表) throws 异常类{}
注:
- throws 关键字写在方法的声明位置
- throws 关键字后必须跟上所发生的异常类
- 如果抛出的是 RuntimeException 及其子类,那么调用者可以不进行异常的处理
- 如果抛出的不是 RuntimeException 及其子类,那么调用必须进行异常的处理(继续抛出、捕获)
- 如果方法中出现了多个异常,可以抛出多个异常类或者这这些异常类的父类
- 重写父类的方法时,如果父类没有抛出异常,那么子类重写时也不能抛出异常
1.3.2 使用 try-catch 捕获异常
格式:
try{ // 可能发生异常的代码(需要进行异常检测的代码) }catch(异常类 异常对象){ // 当try中发生异常时,会立即进行catch,由catch处理异常 }
try{ }catch(){ }finally{ // finlly中的代码一定会执行,一般用于释放资源 }
try{ }finally{ }
注:
- try 代码块用于检查可能发生的异常
- try 中出现异常后会立即进入 catch 代码块
- catch 后的
()
中必须是所发生异常的对象或者这个异常类的父类对象- finlly 中的代码一定会执行,一般用于释放资源
- try、catch、finally 都不能单独使用
- 无论try、catch中是否有返回、返回了什么,只要 finally 中有 return,那么整个方法一定执行的是finally中的 return
1.3.3 try-catch 处理多个异常
多个异常一次处理:直接使用 Exception 处理异常
int[] arr = {0,1,2}; try { int index = new Random().nextInt(5); System.out.println(index); System.out.println(10 / arr[index]); } catch (Exception e){ System.out.println("异常了"); }
多个异常分别处理
int[] arr = {0,1,2}; try { int index = new Random().nextInt(5); System.out.println(index); System.out.println(10 / arr[index]); } catch (ArrayIndexOutOfBoundsException e){ System.out.println("越界了"); } catch(ArithmeticException e){ System.out.println("除零了"); } catch(NullPointerException e){ System.out.println("对象空了"); } catch (Exception e){ System.out.println("未知异常"); }
注:多个 catch 中的异常类如果有继承关系,那么父类异常必须写在子类异常的后面
1.4 异常相关的方法
- String e.toString():返回异常类以及异常的数据
- String e.getMessage():返回异常的数据
- void e.printStackTrace():输出异常类、异常的原因、异常的位置
1.5 自定义异常类
概念:Java 中提供的异常类无法描述当前项目中的特定情况时,可以将这些不符合的情况视作异常进行处理
步骤:
- 创建自定义异常类继承 Exception
- 在该类中添加构造函数和重载的构造函数,并在构造函数中调用父类的构造函数
- 在满足异常发生的情况下,创建自定义异常类的对象,并使用
throw
关键字抛出
12_单例设计模式
一,单例设计模式
1.1 概念
设计模式的概念:解决开发中某些特定问题的固定写法
单例:单个实例,整个应用中有且只有一个该类对象
1.2 步骤
- 构造函数私有化
- 在本类中创建本类对象
- 对外提供一个可以获取本类对象的方法
1.3 饿汉式
public class Singleton1 { private Singleton1(){} private static Singleton1 s = new Singleton1(); public static Singleton1 getInstance(){ return s; } }
1.4 懒汉式
public class Singleton2 { private Singleton2(){} private static Singleton2 s = null; public static Singleton2 getInstance(){ if(s == null){ s = new Singleton2(); } return s; } }
1.5 饿汉式和懒汉式的区别
- 对象创建的时机不同,饿汉式在类加载时创建对象;懒汉式是在调用getInstance()方法时才创建对象
- 即使没有调用饿汉式的getInstance()方法,饿汉式所创建的单例对象也存在,占用资源
- 懒汉式线程不安全
线程安全的懒汉式
public class Singleton2 { private Singleton2(){} private static Singleton2 s = null; public synchronized static Singleton2 getInstance(){ if(s == null){ s = new Singleton2(); } return s; } }
13_集合
一,概念
集合是一种容器
特点:
- 集合的长度是可变的
- 同一个集合可以存储不同的数据类型
- 可以通过泛型来明确集合中存储的数据类型,一旦指定了类型,该集合就只能存储这种类型的数据
- 集合中只能存储引用数据类型,不能存储基本数据类型,要存储基本数据类型必须存储对应的包装类
二,集合的体系
Iterable:迭代器接口,为子接口以及所有实现类提供了迭代的功能
Collection:集合的顶层接口,提供了集合中的基本操作
List:接口,有序可重复
ArrayList:数组结构
LinkedList:链表结构
Set:接口,不可重复
HashSet:哈希表结构,无序的(不能保证存储顺序和获取顺序一致)
TreeSet:树结构
LinkedHashSet:哈希表+链表
三,List 集合
List 的特点:有序可重复
3.1 ArrayList 集合
概念:ArrayList 集合是 List 接口的一个实现类,它的存储结构是数组。查询快,增删慢。
构造函数:
- new ArrayList():构造一个初始容量为 10 的空列表
- new ArrayList(int capacity):构造一个具有指定初始容量的空列表
- new ArrayList(Collection<? extends E> c):根据参数集合构造一个新集合,参数集合的泛型必须是当前所创建集合泛型的类型或它的子类(设置泛型的上限)
常用方法:
- add(E e):将元素添加到集合的末尾,返回布尔值;如果是 List 的 add() 返回值一定是 true,如果是 Set 的add() ,当元素已存在时会返回 false
- add(int index, E e):将元素添加集合中的指定位置,返回 void
- remove(E e):从集合中移除指定对象,返回是否移除成功的布尔值
- remove(int index):从集合中移除指定位置上的对象,返回被移除的对象
- set(int index, E e):用指定元素替换集合中指定位置上的元素
- get(int index):通过下标返回元素
- size():返回集合的大小
- addAll(Collecrtion c)
- addAll(int index, Collection c)
- containsAll(Collection c):判断参数集合中的所有元素是否都存在于调用者集合中
- a . retainAll( b ):将 a、b两个集合的交集替换掉 a 集合中的所有元素,返回 true 表示 a 集合发生了改变
- a . removeAll( b ):从 a 集合中移除 a、b 集合的交集,返回 true 表示 a 集合发生了改变
- clear()
- contains(E e)
- indexOf(Object o)
- lastIndexOf(Object o)
- isEmpty()
3.2 LinkedList 集合
概念:LinkedList 集合是 List 接口的一个实现类,它的存储结构是链表。增删快,查询慢
注:在 ArrayList 中的方法,在 LinkedList 中都有,但是在LinkedList 额外具有一些专门用于操作首尾元素的方法,在多态下,不能使用 LinkedList 中的特有功能。
四,Set 集合
Set 的特点:不可重复,没有下标概念
4.1 HashSet 集合
概念:HashSet 集合是 Set 接口的一个实现类
特点:
- 不允许有重复元素
- 没有下标,没有根据下标来操作集合的相关方法,因此 Set 集合不能使用普通循环遍历,只能通过 foreach 来遍历
- 它是一个无序的集合,不能保证存取顺序一致
- 底层数据结构是哈希表,哈希表特点查询快
- jdk1.8之前:哈希表 = 数组 + 链表
- jdk 1.8及之后:哈希表 = 数组 + 链表(当链表的长度到达8个时,会自动转换成红黑树)
哈希值:是一个十进制数,由系统运算得出,它是根据地址模拟出来的一个值
HashSet 的存储原理:
向 HashSet 存储数据时,会先判断集合中是否存在与要存入元素相同哈希值的元素,如果没有,则直接存储;如果有会继续调用 equals() 方法判断,如果返回 true 就不存储,反之,就存储。
问:如何使用 HashSet 存储自定义数据类型保证对象唯一?
答:重写 hashCode() 和 equals()
@Override public int hashCode() { return Objects.hash(name, age); } @Override public boolean equals(Object obj) { if(this == obj){ return true; } if(obj == null || !(obj instanceof Stu)){ return false; } Stu s = (Stu)obj; return this.age == s.age && Objects.equals(this.name,s.name); }
4.2 TreeSet 集合
概念:TreeSet 集合是 Set 接口中的一个能实现自动排序的实现类
注:如果 TreeSet 存储的是自定义类型的对象,那么该类必须实现 Comparable 接口,否则会发生 ClassCastException。还需要重写 compareTo() 方法
如果 List 集合也要完成对象属性的排序,需要:
方式一:
- 实现 Comparable 接口
- 重写 compareTo() 方法
- 调用集合工具类 Collections.sort( 集合 )
class Stu implements Comparable<Stu>{ @Override public int compareTo(Stu o) { return this.age - o.age; } String name; int age; } Collections.sort(学生集合);
方式二:
- 调用集合工具类 Collections.sort( 集合 ,Comparator c)
- 传入集合对象和 Comparator 的实现类对象
- 重写 compare() 方法
Collections.sort(学生集合, new Comparator<Stu>() { @Override public int compare(Stu o1, Stu o2) { return o1.age - o2.age; } });
4.3 LinkedHashSet 集合
概念:LinkedHashSet 集合是 Set 接口中的一个有序的实现类
能保证有序(存取顺序一致)是原因是 LinkedHashSet 在 HashSet 的基础上多加了一条用于记录存储顺序是链表
五,Map 集合
5.1 概念和特点
Collection 是单列集合
Map 是双列集合,在 Map 中存储元素时,要将键和值形成一一对应的映射关系存储到集合中
Map 的特点:
- Map 中的一个元素包含两部分,键(key)和值(value)
- 键和值的类型是任意的
- 键是唯一的,值是可以重复的
- 键(key)和值(value)所形成的关系就称为一一对应的映射关系,键值对
- Map 没有下标
5.2 常用方法
- put(k,v):将 k、v形成的键值对添加到集合中,当 key 重复时,返回被替换的 value
- remove(k):将键所对应的键值对删除,返回被删除的值
- remove(k,v):只有当k、v的映射关系存在集合于中才会删除
- replace(k,v):将 v 替换指定 k 上的值
- replace(key,oldValue,newValue):只有当 key 和 oldValue 的映射关系存在集合于中才会将 newValue 替换掉 oldValue
- get(k):根据键返回对应的值
- size():返回集合的大小
- clear():清空集合
- containsValue(v):判断是否存在指定的值
- containsKey(k):判断是否存在指定的键
- isEmpty():判断集合是否为空
- equals(map):比较两个 Map 中的元素是否完全相同
- values():返回 Map 中值组成 Collection 集合
- keySet():获取 Map 中所有键组成的 Set 集合
- entrySet():获取 Map 中所有映射关系组成的 Set 集合
六,迭代器
概念:迭代器是遍历集合元素的通用方法
原理:先判断是否有下一个可以迭代的元素,如果没有,则不获取;如果有,则获取下一个元素。
迭代器接口:Iterable ,提供了获取用于执行迭代功能的迭代器对象 Iterator
方法:
- hasNext():判断集合中是否存在下一个可以获取的元素
- next():获取下一个元素
代码:
// 通过Map的keySet方法获取指定Map中所有键组成的Set集合 Set<String> set = map.keySet(); // 通过Set集合的iterator()方法获取迭代器对象,此时这个迭代器就与调用该方法的集合进行了关联 Iterator<String> it = set.iterator(); // 循环判断是否有下一个可以迭代的元素 while(it.hasNext()){ // 获取下一个迭代的元素 String key = it.next(); String value = map.get(key); System.out.println(key + "---"+ value); }
// 通过Map的entrySet方法获取指定Map中所有映射关系组成的Set集合 Set<Entry<String, String>> set = map.entrySet(); // 通过Set集合的iterator()方法获取迭代器对象,此时这个迭代器就与调用该方法的集合进行了关联 Iterator<Entry<String, String>> it = set.iterator(); while(it.hasNext()){ Entry<String, String> en = it.next(); System.out.println(en.getKey()+"---"+en.getValue()); }
注:
- 迭代器也必须有泛型,集合的泛型是什么类型,迭代器的泛型就是什么类型
- 迭代器是所有Collection遍历的通用方法,Collection 接口继承了 Iterable 接口,Iterable 接口中提供了获取迭代器对象的方法 iterator() ,迭代器中提供了 hasNext()、next() 等用于遍历集合的方法。
- hasNext() 返回结果为 false,表示没有下一个可以遍历的元素,如果继续使用 next() 来获取下一个元素会发生 NoSuchElementException。
使用迭代器遍历集合时删除集合中的元素
public static void f2(){ HashMap<String,String> map = new HashMap<String,String>(); map.put("D","2"); map.put("B","4"); map.put("C","1"); map.put("A","3"); map.put("F","3"); System.out.println(map); Set<String> set = map.keySet(); Iterator<String> it = set.iterator(); while(it.hasNext()){ String key = it.next(); if(map.get(key).equals("3")){ it.remove(); } } System.out.println(map); } public static void f1(){ HashMap<String,String> map = new HashMap<String,String>(); map.put("D","2"); map.put("B","4"); map.put("C","1"); map.put("A","3"); map.put("F","3"); System.out.println(map); Set<String> set = map.keySet(); List<String> list = new ArrayList<String>(); list.addAll(set); ListIterator<String> it = list.listIterator(); while(it.hasNext()){ String key = it.next(); if(map.get(key).equals("2")){ map.remove(key); } } System.out.println(map); }
七,Collections
概念:此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。
常用方法:
addAll(Collection<? super T> c ,T … e):将可变参数 e 中的所有元素添加到集合 c 中
binarySearch(List l,T t):使用二分搜索法查询集合中指定元素的下标,
前提是:1.升序排列 2.没有重复元素
copy(List a,List b):将 b 集合中的所有元素赋值到 a 集合从第一个元素开始,如果 a 的长度小于 b 的长度会发生 IndexOutOfBoundsException
fill(List l,T … t):使用 t 替换 集合中的所有元素
max(Collection c):获取集合中最大值
min(Collection c):获取集合中最小值
如果要获取自定义类型中对象属性的最大最小值,可以使用重载形式:
max(Collection c,Comparable c)、min(Collection c,Comparable c)
replaceAll(List l,T oldValue,T newValue):使用 newValue 替换 list 中的所有 oldValue
*reverse():倒置
*swap(List l,int i,int j):将集合中下标 i 和下标 j 位置上的元素交换
*shuffle(List l):打乱集合
*sort(List l):字符串和基本类型默认按照字典顺序排序,自定义数据类型的属性排序时需要实现 Comparable 接口
*sort(List l,Comparator c):字符串和基本类型默认按照字典顺序排序,自定义数据类型的属性排序时,需要传入 Comparator 的实现类对象。
14_多线程
一,概念
进程:正在运行的程序
线程:是进程中的一个执行单元(一条执行路径),一个进程至少包含一条线程。如果一个进程包含多个线程,这种程序就叫多线程程序
并发:两件事同时进行
并行:两件事同时发生
线程的调度:
- 分时调度:所有线程轮流使用CPU,每个线程平均分配CPU的执行权
- 抢占式调度:优先让优先级高的线程执行,优先级相同时,CPU随机分配执行权,Java 中多线程的执行方式就是抢占式的
注:Java 程序在没有额外开启线程的情况下也有两个线程:主函数所在的主线程、垃圾回收线程
二,创建线程
2.1 继承 Thread 类
步骤:
- 定义类继承 Thread 类
- 重写 run() 方法,在 run() 方法中的功能就是线程要执行的功能
- 创建 Thread 的子类对象
- 调用 start() 方法开启线程,Java 虚拟机自动调用该线程的 run() 方法。
注:同一个线程对象不能重复调用 start(),否则会发生 IllegalThreadStateException
public class Test { public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); MyThread mt2 = new MyThread(); mt2.start(); } } class MyThread extends Thread{ @Override public void run() { for(int i = 1;i<=100;i++){ System.out.println(Thread.currentThread().getName()+"-----"+i); } } }
2.2 实现 Runnable 接口
步骤:
- 定义类实现 Runnable 接口
- 重写 run() 方法,在 run() 方法中的功能就是线程要执行的功能
- 创建该实现类的对象
- 创建 Thread 对象,并将实现类的对象作为参数传递给 Thread 的构造函数
- 调用 start() 方法开启线程,Java 虚拟机自动调用该线程的 run() 方法。
public class Test2 { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t = new Thread(mr); t.start(); Thread t2 = new Thread(mr); t2.start(); } } class MyRunnable implements Runnable{ @Override public void run() { for(int i = 1;i<=100;i++){ System.out.println(Thread.currentThread().getName()+"-----"+i); } } }
注:建议优先选用实现 Runnable 的方式,因为避免了单继承的局限性
2.3 使用匿名内部类创建线程并开启
new Thread(){ @Override public void run() { for(int i = 1;i<=100;i++){ System.out.println(Thread.currentThread().getName()+"-----"+i); } } }.start(); new Thread(new Runnable(){ @Override public void run() { for(int i = 1;i<=100;i++){ System.out.println(Thread.currentThread().getName()+"-----"+i); } } }).start();
三,线程安全问题
什么是线程安全:如果多个线程并发执行,这些线程操作共享数据,运行后的结果与单线程运行后的结果是相同的,就称为线程是安全的。
以下代码出现了线程安全问题:限制了ticket > 0的条件,但是最终输出的ticket出现的0和负数,因为某条线程在执行的过程中被其他线程抢夺了CPU的执行权。
class MyThread extends Thread{ static int ticket = 100; @Override public void run() { for(;;){ if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+ (ticket)+"号票"); ticket--; } } } }
class MyRunnable implements Runnable{ int ticket = 100; @Override public void run() { for(;;){ if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+ (ticket)+"号票"); ticket--; } } } }
四,同步技术
关键字:
synchronized
4.1 同步代码块
格式:
synchronized(锁对象){ // 可能发生线程安全问题的代码 }
注:
- 同步代码块中的锁对象(同步锁)可以是任意类型的
- 必须保证多个线程使用的锁对象是同一个
- 锁对象的作用:将同步代码块锁定,同一时间只允许让一个线程进入同步代码块,只要有一个线程抢夺到了CPU的执行权,只有当该线程执行完毕后,其他线程才能继续抢夺CPU的执行权,否则就处于等待状态。
4.2 同步函数
格式:
public synchronized 返回类型 方法名(参数列表){ // 可能发生线程安全问题的代码 }
注:
同步函数中有锁对象吗?有,同步函数中的锁对象是 this
如果使用的是继承 Thread 的方式创建线程,同步函数必须是静态的
静态同步函数的锁对象是谁?锁是
类名.class
二进制字节码文件对象
4.3 Lock 锁
概念:Lock 是一个接口,在 JDK1.5 出现,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构。
方法:
- lock():获取锁
- unlock():释放锁
步骤:
- 在成员位置创建 Lock 接口的实现类对象 ReentrantLock
- 在可能发生线程安全问题的代码前调用 lock()
- 在可能发生线程安全问题的代码后调用 unlock()
同步技术的原理:
同步技术中使用到的锁对象,这个锁对象也称为同步锁
多个线程一起抢夺CPU的执行权
获取到锁对象的线程,进入同步,当这个线程执行完同步中的代码后,释放锁;
没有获取到锁对象的线程,会处于阻塞状态,等待同步中的线程执行完毕后释放锁;
使用同步技术会影响程序的执行效率:
判断锁、获取锁、释放锁
五,线程池
概念:线程池的本质就是一个容器,在该容器中存放着若干线程对象,当有任务需要使用线程对象时,直接从池中获取线程对象,当任务执行完毕后,将使用完的线程对象归还到池中,从而提高了线程对象的复用性,减少了线程对象的创建。
线程池的好处:
- 减低了资源的消耗。减少了创建和销毁线程的次数,每个线程对象都可以被复用
- 提高了响应速度
- 提高了对线程的管理
核心类:Executors,线程池的工厂类,用于生产线程池
Executors 类中提供了生产线程池的静态方法 newFixedThreadPool(int nThreads),创建一个有固定数量线程对象的线程池,返回 ExecutorService 接口的实现类。
ExecutorService 接口中提供获取线程对象的功能 submit(Runnable r),调用此功能会开启线程并执行 Runnable 实现类中的 run() 方法
MyRunnable1 mr1 = new MyRunnable1(); MyRunnable2 mr2 = new MyRunnable2(); MyRunnable3 mr3 = new MyRunnable3(); ExecutorService service = Executors.newFixedThreadPool(2); service.submit(mr1); service.submit(mr2); service.submit(mr3); service.shutdown();// 关闭线程池
注:
- 如果要开启线程执行的任务超过池中线程对象的数量,没有执行的任务会等待其他任务执行完毕归还线程对象再执行它的任务
- 即使线程对象已经全部归还,线程池仍然处于开启状态,因为线程池在等待可能还有其他任务需要使用池中对象,我们可以手动调用 shutdown() 来强制关闭池,一旦池被关闭了,再从池中获取连接对象就会发生 RejectedExecutionException。
15_文件、IO流
一,File
1.1 概念
概念:文件和目录路径名的抽象表示形式。
1.2 构造函数
- new File(String path)
- new File(File parent,String path)
- new File(String parent,String path)
字段摘要:
File.separator:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
1.3 常用方法
1.3.1 创建和删除
- createNewFile():如果指定文件不存在,则创建一个指定文件名的文件,返回 true;如果已存在,则不创建,返回 false
- mkdir():创建一个文件夹(只能创建单层目录),返回 false 表示创建失败,true 表示成功
- mkdirs():创建一个文件夹(可以创建多级目录),返回 false 表示创建失败,true 表示成功
- delete():删除文件或者文件夹,,返回 false 表示删除失败,true 表示成功,如果文件夹中含有内容,那么该文件夹不能删除
- deleteOnExit():删除文件或者文件夹,在程序运行完毕后执行该删除操作,如果文件夹中含有内容,那么该文件夹不能删除
1.3.2 判断相关
- exists():判断文件或者文件夹是否存在
- isFile():判断是否是文件
- isDirectory():判断是否是目录
注:如果文件或者文件夹不存在,那么无论是判断是文件还是目录返回的都是 false
- canRead():判断是否可读
- canWrite():判断是否可写
- canExecute():判断是否可执行,只有 linux 操作系统下有效
- isHidden():判断文件或者文件夹是否隐藏
1.3.3 获取相关
- getPath():获取路径,返回结果与创建时的路径表示形式相同
- getAbsolutePath():获取绝对路径,无论创建时使用的是相对路径还是绝对路径,返回的都是绝对路径
- getParent():返回父目录的地址,如果是以相对路径的写法创建文件对象,那么返回的结果是null
- getParentFile():返回父目录文件的对象
- getName():返回文件名(包含后缀)
- length():返回文件大小,单位是字节
1.3.4 遍历目录
- list():返回指定目录下所有文件或者文件夹的名字组成的数组
- listFiles():返回指定目录下所有文件对象组成的数组
- list(FilenameFilter filter):根据指定的文件名过滤器获取指定文件,将它们的名字组成数组
- listFile(FilenameFilter filter):根据指定的文件名过滤器获取指定文件,将这些文件对象组成数组
1.4 文件过滤
实现 FileNameFilter 接口
class MyFilter implements FilenameFilter{ /* * 参数: * 1.dir:调用listFiles的目录对象 * 2.name:当前目录下的所有文件的文件名 */ @Override public boolean accept(File dir, String name) { return name.endsWith("mp3")||name.endsWith("wma"); } }
实现 FileFilter 接口
File[] fss = f.listFiles(new FileFilter(){ @Override public boolean accept(File pathname) { return pathname.getName().endsWith("txt"); } }); System.out.println(fss.length);
1.5 目录的遍历(递归)
递归:在方法中直接或者间接的调用自身
注:
- 递归要有结束的条件,保证递归能够停止,否则会发生 StackOverflowError 栈溢出
- 在不断递归的过程中,数据规模要不断减小
- 递归的性能较差
public static void search(File dir){ for (File file : dir.listFiles()) { if(file.isFile()){ if(file.getName().endsWith("txt")){ fileList.add(file); } }else{ search(file); } } }
二,IO流
2.1 概念
我们把数据的传输看成是一种数据的流动,按照指定的流向划分问输入流(input)和输出流(output)
IO流的分类:
根据流向划分:输入流和输出流
- 输入流:把数据从其他设备上读取到内存中(外 -> 内)
- 输出流:把数据从内存中写到其他设备(内 -> 外)
根据数据的类型划分:字节流和字符流
- 字节流:以字节为单位,任何文件都可以使用字节流进行读写
- 字符流:以字符为单位,一般只对文本进行读写
IO流的顶层父类:它们都是抽象类
- 字节输入流:InputStream
- 字节输出流:OutputStream
- 字符输入流:Reader
- 字符输出流:Writer
2.2 FileOutputStream
概念:FileOutputStream 是一个专门针对文件进行写操作的流
构造方法:
- new FileOutputStream(File f):根据指定文件对象所指向的文件路径创建文件字节输出流对象
- new FileOutputStream(String name):根据指定文件路径创建文件字节输出流对象
- new FileOutputStream(File f,boolean b):功能同上,第二个参数值为 true 时,表示该文件可以续写
- new FileOutputStream(String name,boolean b):功能同上,第二个参数值为 true 时,表示该文件可以续写
常用方法:
- close():释放资源,如果执行了 close() 方法,那么就不能在写入了
- flush():刷新缓冲区
- write(int ch):根据十进制数写入对应的字符
- writer(byte[] b):写入指定的字节数组,如果要写入字符串,可以通过,字符串的 getBytes() 方法获取对应的字节数组
- writer(byte[] b,int index ,int lenth):从字节数组中的指定位置获取指定数量的元素写入到文件中
2.3 FileInputStream
概述:该类是 InputStream 的一个专门针对文件进行读取的流,它定义了一系列与读取相关的方法。
构造函数
- new FileInputStream(File f):根据指定文件对象所指向的文件路径创建文件字节输入流对象
- new FileInputStream(String name):根据指定文件路径创建文件字节输入流对象
常用方法
- read():读取单个字符所对应的十进制数,返回 -1 表示读完了
- read(byte b[],int a,int b):从指定文件中的a位置开始,读取b个字符,存储到数组中
- read(byte b[]):从指定文件中读取还未获取到的字符,存储到数组中,返回读取到的有效个数
- close():释放资源
复制练习:
public class Test { // 将C盘中的文件拷贝(剪切)到D盘中 public static void main(String[] args) throws Exception{ Copy c1 = new Copy("C:\\mine\\Java\\idea\\ideaIU-2017.3.4.exe","C:\\Users\\86151\\Desktop\\2009\\test\\idea.exe"); Copy c2 = new Copy("C:\\mine\\Java\\hbuilder\\HBuilder.8.8.4.windows.zip","C:\\Users\\86151\\Desktop\\2009\\test\\hbuilder.zip"); c1.start(); c2.start(); } } class Copy extends Thread{ private String inputPath; private String outputPath; public Copy(String inputPath, String outputPath) { this.inputPath = inputPath; this.outputPath = outputPath; } @Override public void run() { { File f = new File(inputPath); FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream(f); fos = new FileOutputStream(outputPath); int len; byte b[] = new byte[1024]; while((len = fis.read(b)) != -1){ fos.write(b,0,len); } } catch (Exception e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2.4 FileReader
概念:该类是 Reader 的一个专门针对文件进行读取的流,它定义了一系列与读取相关的方法。
构造函数
- new FileReader(File f)
- new FileReader(String name)
常用方法
- read()
- read(char ch[])
- read(char ch[] ,int index,int lenth)
- close()
2.5 FileWriter
概念:该类是 Writer 的一个专门针对文件进行写出的流,它定义了一系列与写出相关的方法。
构造函数
- new FileWriter(File f)
- new FileWriter(String name)
- new FileWriter(File f,boolean b)
- new FileWriter(String name,boolean b)
常用方法
- close():释放资源,执行释放动作前,会先将缓冲区中的数据刷新到的目标位置
- flush():当使用 write() 方法写入文件时,只是把数据写入到了缓冲区中,需要用 flush() 才能将数据从缓冲区中真正的刷新到目标位置
- write(int ch)
- write(String str)
- write(String str,int index,int length)
- write(char[] ch)
- write(char[] ch,int index,int length)
close() 与 flush() 的区别:
flush() :只是将数据从缓冲区中刷新到目标位置,刷新后,该输出流仍能正常使用
close():会先将缓冲区中的数据刷新到的目标位置,再关闭资源,关闭后,该流不能再使用
2.6 Properties 属性集
概念:Properties 是 Map 的一个实现类,它用来表示持久的属性集,它使用键值对的方法存储数据
注:
- Properties 一般用于读取配置文件
- Properties 中的键和值只能是字符串类型的
- Properties 中的键和值的格式必须是:
- key=value
- key:value
- key value
构造函数:new Properties()
方法:
- load(InputStream is)
- load(Reader r)
- getProperty(String key)
配置 properties 文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/dbname username=root password=1234
读取:
Properties pro = new Properties(); // FileReader fr = new FileReader(); // pro.load(fr); FileInputStream fis = new FileInputStream("src\\jdbc.properties"); pro.load(fis); System.out.println(pro.getProperty("driver")); System.out.println(pro.getProperty("url")); System.out.println(pro.getProperty("username")); System.out.println(pro.getProperty("password"));
2.7 缓冲流
概念:缓冲流,也叫高效流,它是对 FileXxx 的增强
缓冲流的原理:在创建流对象的同时,会创建一个内置的缓冲区数组,通过缓冲区的读写,减少IO的操作次数,从而提高读写效率
分类:
- BufferedReader:字符缓冲输入流
- BufferedWriter:字符缓冲输出流
- BufferedInputStream:字节缓冲输入流
- BufferedOutputStream:字节缓冲输出流
构造函数:
BufferedReader br = new BufferedReader(Reader r); BufferedWriter bw = new BufferedWriter(Writer w); BufferedInputStream bis = new BufferedInputStream(InputStream is); BufferedOutputStream bos = new BufferedOutputStream(OutputStream os);
方法:缓冲流中的方法和普通文件流中的方法相同
特有方法:
- BufferedReader 中 readLine():读取一整行
- BufferedWriter 中 newLine():写入一个换行
public static void copy2()throws Exception{ BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\mine\\Java\\idea\\ideaIU-2017.3.4.exe")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\86151\\Desktop\\2009\\test\\idea.exe")); byte[] b = new byte[1024]; int len; while((len = bis.read(b))!=-1){ bos.write(b,0,len); } bis.close(); bos.close(); }
2.8 转换流
类:InputStreamReader、OutputStreamWriter
字符编码:在屏幕上看到的任何字符在计算机底层都是以某种规则转换成二进制数的形式出现的,这种转换方法就称为编码。将计算机底层的二进制数以某种规则转换成显示在屏幕上的字符,这种转换方式叫解码。如果转换规则不匹配就会发生乱码。
简单的说字符编码就是一套字符与二进制数转换的规则。
字符集:也叫编码表。
ASCII:它是一个基于拉丁字母的一套计算机编码系统,共128字符
ISO8859-1:ISO-8859-1编码是单字节编码,向下兼容ASCII
GB2312:简体中文码表
GBK:常用中文码表,在是在 GB2312 上进行扩充
UTF-8:电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码
InputStreamReader
概念:它是Reader的子类,它是读取字节,并使用指定的字符串将其转换成字符
构造函数:
- new InputStreamReader(InputStream is)
- new InputStreamReader(InputStream is,String charset)
OutputWriter
概念:它是Writer的子类,它读取字符,并使用指定的字符集将其转换成字节
构造函数
- new OutputStreamWriter(OuputStream os)
- new OutputStreamWriter(OuputStream os,String charset)
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\86151\\Desktop\\2009\\test\\c.txt"),"gbk"); osw.write("你好"); osw.flush(); InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\86151\\Desktop\\2009\\test\\c.txt"),"gbk"); char[] chs = new char[2]; int len; while((len = isr.read(chs))!=-1){ System.out.println(new String(chs,0,len)); }
2.9 对象流(序列化流)
Java 中提供了一种对象的序列化机制,允许将一个对象及其属性持久化到文件中
序列化:将对象存储到文件中
反序列化:将文件中的对象解析出来
ObjectOutputStream
构造函数:new ObjectOuputStream(OutputStream os)
ObjectInputStream
构造函数:new new ObjectInputStream(InputStream is);
Emp e = new Emp(); e.setName("张三"); e.setAge(20); FileOutputStream fos = new FileOutputStream("C:\\Users\\86151\\Desktop\\2009\\test\\d.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(e); FileInputStream fis = new FileInputStream("C:\\Users\\86151\\Desktop\\2009\\test\\d.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Emp emp = (Emp)ois.readObject(); System.out.println(emp.getName()); System.out.println(emp.getAge());
注:
- 一个类的对象要被序列化,该类必须实现 Serializable ,该接口是一个标记接口,不需要重写方法,否则会发生 NotSerializableException
- 如果读取对象的次数超过了对象的个数会发生 EOFException
- 如果不想让某个属性被序列化,需要使用关键字
transient
(瞬态的)
16_ 网络编程
一,网络中的三要素
ip地址:
ip 地址是物理地址,用于标识网络中的计算机设备
查看本机ip地址:cmd -> ipconfig
ipv4:是一个32位的二进制数,表现形式:xxx.xxx.xxx.xxx,最多能标识42亿多个地址
ipv6:每16个字节为一组,共8组,表现形式:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,每个x的范围0~f
本机的ip可以用:localhost、127.0.0.1 表示
端口号:
端口号用于标识计算机中的应用
端口的范围:065535,特殊的端口号:01023之间的端口号一般用于一些知名的或者系统应用
格式:ip:端口号
例如:10.11.52.36:8080
传输协议:
数据在网络中进行传输时需要遵循的规则
在 java.net 包中定义使用 UDP、TCP 协议实现数据传输的类
二,UDP
UDP:用户数据报协议(User Datagram Protocol)
特点:
- 无需建立连接,发送端不会去确认接收端是否存在
- 速度快
- 不可靠
- 应用:在线视频
- 代码:传输的数据必须与目标地址封装在同一个数据包对象中
核心类:
- DatagramSocket:封装了数据包的接收和发送
- DatagramPacket:数据包,封装了要传输的数据,如果是发送端还需要将接收端的目标地址封装在里面
三,TCP
TCP:传输控制协议(Tranfer Control Protocol)
特点:
- 经历三次握手保证连接的建立
- 速度慢
- 可靠
- 应用:下载
核心类:
- ServerSocket:服务端
- Socket:客户端
四,InetAddress
概念:InetAddress 类是 java.net 包下的一个用于获取域名、ip地址的类
方法:
- getByName(String host):host 可以是域名 www.baidu.com,也可以是ip地址 10.11.52.36
- getLocalHost():获取本机的域名、ip
- getAllByName(String host):获取指定域名或者ip地址所对应的所有ip地址
五,UDP 通信
// 收 new Thread(){ @Override public void run() { try { DatagramSocket ds = new DatagramSocket(7777); while(true){ byte[] b = new byte[1024]; DatagramPacket dp = new DatagramPacket(b,0,b.length); ds.receive(dp); byte[] data = dp.getData(); System.out.println(new String(data)); } } catch (Exception e){ e.printStackTrace(); } } }.start(); // 发 new Thread(){ @Override public void run() { try { while(true){ DatagramSocket ds = new DatagramSocket(); String msg = new Scanner(System.in).next(); byte[] data = msg.getBytes(); DatagramPacket dp = new DatagramPacket(data,0,data.length, InetAddress.getByName("10.11.52.36"),6666); ds.send(dp); } }catch (Exception e){ e.printStackTrace(); } } }.start();
六,TCP 通信
客户端:
public static void main(String[] args)throws Exception { Socket s = new Socket("10.11.52.36",1234); // 收 new Thread(){ @Override public void run() { try { while(true){ InputStream is = s.getInputStream(); byte[] data = new byte[1024]; int len = is.read(data); System.out.println(new String(data)); } }catch (Exception e){ } } }.start(); // 发 new Thread(){ @Override public void run() { try { while(true){ OutputStream os = s.getOutputStream(); String msg = new Scanner(System.in).next(); os.write(msg.getBytes()); } }catch (Exception e){ } } }.start(); }
服务端
public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(1234); Socket s = ss.accept(); // 收 new Thread(){ @Override public void run() { try{ while(true){ InputStream is = s.getInputStream(); byte[] data = new byte[1024]; int len = is.read(data); System.out.println("服务器接收到了"+s.getInetAddress().getHostAddress()+":"+new String(data,0,len)); } }catch (Exception e){ } } }.start(); // 发 new Thread(){ @Override public void run() { try { while(true){ OutputStream os = s.getOutputStream(); String msg = new Scanner(System.in).next(); os.write(msg.getBytes()); } }catch (Exception e){ } } }.start(); }
6.1 聊天室
private static ArrayList<Socket> sockets; public static void main(String[] args) throws Exception { System.out.println("服务器开启"); sockets = new ArrayList<Socket>(); ServerSocket ss = new ServerSocket(7890); new Thread(new Runnable() { int count = 1; @Override public void run() { try{ while(count <= 3){ Socket s = ss.accept(); sockets.add(s); sendAll(s); // 谁在什么时候进入了聊天室 new Thread(new Runnable() { @Override public void run() { try{ while(true){ InputStream is = s.getInputStream(); byte[] data = new byte[1024]; int len = is.read(data); String msg = new String(data,0,len); sendAll(s,msg); // 谁在什么时候说了什么 } }catch (Exception e){ } } }).start(); count++; } }catch (Exception e){ } } }).start(); } public static void sendAll(Socket s,String str) throws Exception{ String ip = s.getInetAddress().getHostAddress(); String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); String msg = time + " " + ip + ":" + str; log(msg); for(Socket socket : sockets){ OutputStream os = socket.getOutputStream(); os.write(msg.getBytes()); } } public static void sendAll(Socket s) throws Exception{ String ip = s.getInetAddress().getHostAddress(); String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); String msg = ip + "在" + time + "加入了聊天室"; log(msg); for(Socket socket : sockets){ OutputStream os = socket.getOutputStream(); os.write(msg.getBytes()); } } public static void log(String msg) throws Exception{ BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\Users\\86151\\Desktop\\log.txt",true)); bw.write(msg); bw.newLine(); bw.flush(); }
6.2 文件上传
客户端
/* 步骤: 1.创建本地的文件输入流 2.创建客户端对象 3.创建网络输出流对象 4.使用本地的文件输入流对象读取图片 5.使用网络输出流对象输出图片 6.创建网络输入流对象 7.使用网络输入流对象读取服务器的回写数据 */ public static void main(String[] args) throws Exception { // 1.创建本地的文件输入流 FileInputStream fis = new FileInputStream("C:\\mine\\图片\\封面.png"); // 2.创建客户端对象 Socket socket = new Socket("10.11.52.36",8888); // 3.创建网络输出流对象 OutputStream os = socket.getOutputStream(); // 4.使用本地的文件输入流对象读取图片 // 5.使用网络输出流对象输出图片 byte[] data = new byte[1024]; int len; while((len = fis.read(data)) != -1){ os.write(data,0,len); } // 客户端无法接收服务器的回写数据 // 原因;本质上是服务器根本没有执行到回写的语句,必须在客户端发送完毕后,告知服务器,发送给服务器一个发送完毕的标记,服务器才能得知接收完毕 socket.shutdownOutput(); // 6.创建网络输入流对象 InputStream is = socket.getInputStream(); byte[] msg = new byte[20]; int length = is.read(msg); System.out.println(new String(msg,0,length)); is.close(); os.close(); fis.close(); socket.close(); }
服务端
/* 步骤: 1.创建服务器对象 2.接收客户端对象 3.创建网络输入流 // 4.判断“同学们的好图”目录是否存在,不存在,则创建 File f = new File("C:\\Users\\86151\\Desktop\\同学们的好图"); if(!f.exists()){ f.mkdir(); } 5.创建本地文件输出流 6.使用网络输入流读取客户端发来的数据 7.使用本地文件输出流向指定目录中写入数据 8.创建网络输出流 9.使用网络输出流向客户端发送回写数据 */ // 1.创建服务器对象 ServerSocket ss = new ServerSocket(9996); while(true){ // 2.接收客户端对象 Socket s = ss.accept(); new Thread(new Runnable() { @Override public void run() { try{ // 3.创建网络输入流 InputStream is = s.getInputStream(); // 指定图片的名字 String filename = new SimpleDateFormat("yyyyMMddHHmmssSS").format(new Date())+new Random().nextInt(100000000)+".jpg"; // 5.创建本地文件输出流 FileOutputStream fos = new FileOutputStream("C:\\Users\\86151\\Desktop\\同学们的好图\\"+filename); // 6.使用网络输入流读取客户端发来的数据 // 7.使用本地文件输出流向指定目录中写入数据 byte[] data = new byte[1024]; int len; while((len = is.read(data)) != -1){ fos.write(data,0,len); } // 8.创建网络输出流 OutputStream os = s.getOutputStream(); os.write("上传成功".getBytes()); os.close(); fos.close(); is.close(); }catch (Exception e){ System.out.println("上传失败"); } } }).start(); }
17_反射
一,概念
Java提供了一种反射机制,是指程序在运行状态下,对于任何一个类,都能知道这个的所有属性和方法;对于任何一个对象都能调用它的任意属性和方法。这种动态获取对象,动态调用属性和方法的机制就称为反射。
注:要剖析一个类的内部结构,必须先获取这个类的字节码文件对象
二,获取字节码文件对象
通过对象获取
Stu s = new Stu(); Class c1 = s.getClass();
通过类名获取
Class c2 = Stu.class;
通过Class类的静态方法获取
Class c3 = Class.forName("com.qf.domain.Stu"); Class c4 = Class.forName("com.mysql.jdbc.Driver");
注:一个类的字节码文件对象是唯一的
三,通过反射获取构造函数,并创建对象
通过 Class 对象可以调用以下方法来获取构造函数对象:
1.getConstructors():返回所有公共的构造函数对象组成的数组 2.getDeclaredConstructors(): 返回所有构造函数对象组成的数组 3.getConstructor(Class...c):返回指定参数的公共的构造函数对象 4.getDeclaredConstructor(Class...c):返回指定参数的构造函数对象
通过构造器对象创建指定类型对象:
1.newInstance(Object...o):根据指定构造器的参数传入实参创建该类对象 2.setAccessible(boolean b):暴力反射,忽略了范围修饰符 注:不建议使用setAccessible去范围私有成员,因为破坏了类的封装性
补充:
Class 中也有newInstance方法可以创建指定类的对象,但是只能通过默认的构造函数来创建
四,通过反射获取成员变量,并赋值
通过 Class 对象可以调用以下方法来获取成员变量对象:
1.getFields():返回所有公共的成员变量对象组成的数组 2.getDeclaredFields():返回所有成员变量对象组成的数组 3.getField(String 成员变量名):返回公共的指定成员变量名的成员变量对象 4.getDeclaredField(String 成员变量名):返回指定成员变量名的成员变量对象
通过成员变量对象可以为指定对象的属性赋值:
1.set(Object obj,Object value) 2.setAccessible(boolean b):暴力反射,忽略了范围修饰符
五,通过反射获取成员方法,并调用
通过 Class 对象可以调用以下方法来获取成员方法对象:
1.getMethods():返回本类中所有公共的方法以及继承下来的,方法对象组成的数组 2.getDeclaredMethods():返回本类中所有方法对象组成的数组(不含继承下来的) 3.getMethod(String 方法名,Class...c):返回公共的指定方法名和参数列表的方法对象 4.getDeclaredMethod(String 方法名,Class...c):返回指定方法名和参数列表的方法对象
通过成员方法对象可以调用指定的方法:
1.invoke(Object obj,Object...values): 2.setAccessible(boolean b):暴力反射,忽略了范围修饰符
六,代理设计模式
6.1 概念
将核心功能与辅助功能相分离,达到核心功能更加纯粹、辅助功能可以重用
核心业务功能必须遵循的原则:单一职能原则
6.2 静态代理
通过代理类的对象,为目标类的对象添加辅助功能
好处:方便更换代理类,核心功能和辅助功能更容易维护和扩展
注:目标类和代理类必须实现同一个接口
接口:
public interface RentOut { void rentOut(); }
目标类:
public class LandLord implements RentOut{ @Override public void rentOut() { System.out.println("签合同"); System.out.println("收租金"); } }
代理类:
public class Agent implements RentOut{ private LandLord lord; public Agent(LandLord lord) { this.lord = lord; } @Override public void rentOut() { System.out.println("发布租房信息"); System.out.println("联系房客"); System.out.println("带房客看房"); lord.rentOut(); System.out.println("拿回扣"); } }
注:
- 代理类:实现与目标类相同的接口,添加辅助功能,调用目标类的核心功能
- 静态代理存在的问题:
- 代理类过多,不易维护
- 多个代理类中的辅助功能可能会出现冗余代码
6.3 动态代理
6.3.1 JDK 动态代理(基于接口)
LandLord lord = new LandLord(); InvocationHandler handler = new InvocationHandler(){ /* 返回类型: Object:代理对象目标对象进行功能增强时,所增强功能的返回值 参数: 1.Object:代理对象 2.Method:代理对象对目标对象进行增强的方法 3.Object[]:代理对象对目标对象进行增强的方法中的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("method:"+method); System.out.println("args:"+Arrays.toString(args)); // 明确增强的功能 System.out.println("发布租房信息"); System.out.println("联系房客"); System.out.println("带房客看房"); // 目标的核心功能 Object o = method.invoke(lord,args); System.out.println("收回扣"); return o; } }; /* 参数: 1.通过任何一个类获取的类加载器 2.接口字节码文件对象数组:要保证代理对象和目标对象实现同样的接口 3.具体增强方法的InvocationHandler对象 */ RentOut obj = (RentOut) Proxy.newProxyInstance(Test.class.getClassLoader(), lord.getClass().getInterfaces(), handler); System.out.println(obj.rentOut(10000,"张三"));
6.3.2 Cglib 动态代理(基于继承)
目标类:
public class Company { public void zp(){ System.out.println("办理入职"); System.out.println("开通企业邮箱"); } }
final Company com = new Company(); Enhancer en = new Enhancer(); en.setSuperclass(com.getClass()); en.setCallback(new InvocationHandler(){ public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("核酸检测"); Object obj = method.invoke(com,objects); return obj; } }); Object o = en.create(); Company c = (Company)o; c.zp();
注:使用jdk的方式创建代理对象与静态代理方式都需要使目标类和代理类实现相同的接口;Cglib 的方式创建代理对象不需要实现接口,因为 Cglib 创建代理对象的本质是创建了目标类的子类作为代理类。
18_java新特性
一,函数式编程思想
1.1 概念
在数学中,函数就是一套计算方案。它的重点在“拿什么东西就能做什么事情”。
对于面向对象的编程思想而言,我们必须明确是哪一个类来做这件事情。
1.2 函数式编程思想与面向对象思想比较
new Thread(new Runnable() { @Override public void run() { for(int i = 1;i <= 100;i++){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"------"+i); } } }).start();
new Thread(()->{ for(int i = 1;i <= 100;i++){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"------"+i); } }).start();
如果要使用实现 Runnable 接口的方式开启线程,我们必须创建出 Runnable 的实现类对象,并重写 run() 方法在 run() 方法中明确线程要执行的任务。我们的目标是开启线程,让线程执行指定的任务,而不是创建Runnable 的实现类对象也不是去重写 run() 方法。实际上,只有 run() 方法体中的内容才是重点。
二,Lambda 表达式
2.1 语法
Lambda 表达式的使用前提:
使用 Lambda 表达式必须要有接口,并且该接口中有且只有一个抽象方法。这种接口称为
函数式接口
注:函数式接口可以通过注解 @FunctionalInterface 来校验
方法的参数必须是函数式接口,才能使用 Lambda 表达式创建接口的实例。
语法组成:
- ():参数列表
- ->:箭头
- {}:代码块
格式:
(参数列表)->{代码块}
格式说明:
- ():表示接口中抽象方法的参数列表(形参);方法没有参数的话就留空,有的话就按照接口中的这个方法的参数列表的顺序来写。
- ->:传递的意思,将参数传递给方法体
- {}:重写接口抽象方法的方法体
Lambda 表达式中可以省略的内容:
- 参数列表的类型可以省略
- 如果参数只有一个,那么
()
能省略,此时类型一定不能写- 如果重写的方法体中只有一条语句,
- 重写的方法没有返回值,可以省略
{}
,此时语句的分号也必须省略- 重写的方法有返回值:可以省略
{}
,此时return
和分号必须省略
2.2 常用的函数式接口
Supplier :它是一种生产型接口
抽象方法:
T get():用于获取一个指定泛型结果的方法
Consumer :它是一种消费型接口
抽象方法:
void accept(T t):用于操作指定泛型的方法
默认方法:
Consumer<T> andThen(Consumer<T> after)
Predicate:它是一个用于判断的接口
抽象方法:
boolean test(T t)
默认方法:
Predicate<T> and(Predicate<T> after) Predicate<T> or(Predicate<T> after)
Function<T,R>:它是用来根据一种类型得到另一种类型的数据
抽象方法:
R apply(T t)
默认方法:
Function<T,R> andThen(Function<T,R> other)
三,Stream流
3.1概念
Stream 是根据Lambda表达式引入的一个全新概念,专门用于解决已有集合的弊端。
集合中最频繁的操作就是遍历集合。要遍历集合就需要通过循环来实现,而循环不是遍历集合的唯一手段,我们的目的也是不是去写循环,是去获取集合中的元素。
3.2 获取 Stream 流对象
- 所有 Collection 集合都可以通过 stream() 方法获取流对象
- Stream 接口中提供了静态方法 of() 来获取数组的流对象
3.3 常用方法
- 延迟方法:返回值仍是Stream对象的方法,可以使用链式写法
- 终结方法:返回值的不是Stream对象的方法,不支持链式写法
- count()
- forEach()
forEach 方法:
void forEach(Consumer<? super T> action);
filter 方法:
Stream<T> filter(Predicate<? super T> predicate);
该方法返回的是 Stream 流对象,而这个方法中的参数是一个用于进行判断是函数式接口,因此只有在判断结果为 true 时,才会将该数据加入到 Stream 流对象中。
count 方法:
long count():用于返回流中对象的个数
map 方法:将流中的元素映射到另一个指定类型的流中
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
limit 方法:对流进行截取,获取指定的前几个
Stream<T> limit(long maxSize);
skip 方法:对流进行截取,跳过前几个
Stream<T> skip(long n);
concat 静态方法:将两个流组合成一个新的流
Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);
四,方法引用
在使用 Lambda 表达式的时候,方法体中的内容是“拿什么参数去执行什么功能”,但是有时会出现冗余代码,此时就可以使用
方法引用
来进一步简化 Lambda 表达式。
4.1 方法引用符号
双冒号
::
就是引用运算符(方法引用符),它所在的表达式就称为方法引用表达式
。如果 Lambda 要执行功能已存在与某一个方法中,并且调用该方法的对象也已经存在,那么就可以使用方法引用来代替Lambda表达式。
4.2 通过对象引用成员方法
函数式接口:
public interface Print { void print(String str); }
定义已存在的对象和方法:
public class PrintClass { public void printSkipStr(String str){ System.out.println(str.substring(3)); } }
测试类:
public class Test { public static void main(String[] args) { /*printStr((s)->{ PrintClass pc = new PrintClass(); pc.printSkipStr(s); },"abcdefg123456789");*/ PrintClass pc = new PrintClass(); printStr(pc::printSkipStr,"123456789"); } public static void printStr(Print p,String str){ p.print(str); } }
4.3 通过类名引用静态方法
函数式接口:
public interface Calc { double calcPow(int a,int b); }
测试类:
public class Test { public static void main(String[] args) { /*double num = f((m,n)->{return Math.pow(m,n);},3,4); System.out.println(num);*/ System.out.println(f(Math::pow,5,2)); } public static double f(Calc c,int a,int b){ return c.calcPow(a,b); } }
4.4 通过super引用成员方法、通过this引用本类的成员方法
函数式接口:
public interface Show { void show(); }
父类:
public class Phone { public void sendMsg(){ System.out.println("发短信"); } }
子类:
public class SmartPhone extends Phone{ @Override public void sendMsg() { System.out.println("发彩信"); } private void a(Show s){ s.show(); } public void b(){ /*a(()->{ super.sendMsg(); this.sendMsg(); });*/ // a(super::sendMsg); a(this::sendMsg); } }
测试类:
public class Test { public static void main(String[] args) { SmartPhone sp = new SmartPhone(); sp.b(); } }
4.5 类的构造函数的引用
实体类:
public class Emp { private String name; private int age; }
函数式接口:
public interface EmpBuilder { // Emp buildEmp(String name,int age); Emp buildEmp(int age); }
测试类:
public class Test { public static void main(String[] args) { /*Emp e = createEmp("张三",20,(name,age)->{return new Emp(name,age);}); System.out.println(e);*/ Emp e = createEmp("张三",3,Emp::new); System.out.println(e); } public static Emp createEmp(String name,int age,EmpBuilder eb){ return eb.buildEmp(age); } }
19_web前端
0,HBuilder
下载:https://www.dcloud.io
安装:解压缩
创建web项目,目录结构:
1.css:存放css文件 2.js:存放js文件 3.img:存放图片 4.index.html:索引页、首页 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> </body> </html>
快捷键:
自动补全:标签名 按 tab
生成多个标签:标签名后,加上*,加上个数,按 tab
补充:a{超链接$$}*7 ,会自动生成内容是:超链接1~7的7个超链接标签
生成测试文本:lorem后加上个数,按 tab
一,html
1.1 概述
html:hypertext markup language,超文本标记语言
超文本:除了纯文本之外,还可以放入其他组件,例如:超链接、图片、表格、按钮
标记:html 中的组件(元素)都是以标记(标签)的形式出现的
<a></a>、<img>
作用:用于绘制页面
1.2 特点
- html 是以标签组成的
- html 文件的后缀是.html或者.htm
- html 由浏览器解释运行的
- html 中的标签都是预定义的
1.3 标签
分类:
单标签:只有开始标签
<img>、<br>、<input>
双标签:由开始标签和结束标签组成
<a></a>、<form></form>
注:
无论是单标签还是双标签,标签的属性都必须写在开始标签中
格式:属性名=“属性值”
<font color="pink">字体1</font> <font color="red">字体2</font> <button style="color: blue;background: lawngreen;">按钮</button>
1.5 html 页面的组成
- 头部:head标签,用于对当前页面进行配置,例如:字符集、刷新频率、标题
- 主体:body标签,用于承载页面上要显示的内容
1.6 注释
<!-- 注释内容 -->
1.7 常用标签
1.7.1 字体标签
标签名:font
属性:
size:设置文本的字号,数值范围1~7,1最小,7最大,默认大小是3
color:设置文本的颜色
使用颜色的单词
使用rgb,格式:#rrggbb,注:如果形式类似于 #ffaa88,可以简化成 #fa8
使用css样式,格式:style=“样式名 : 样式值 ; 样式名 : 样式值 ; …”
注:使用css样式设置颜色时,可以调用颜色的方法:rgb()、rgba()
1.7.2 标题标签
标签名:h,h1~h6
特点:
- 标题标签属于块级标签:独占一整行,上下自动换行
- 自动加粗
- h1~h6:逐渐变小
常见的块级标签:
p、h、br、hr、table、ul、ol、tr、form、div
常见的行内标签:
img、a、input、button、font
1.7.3 图片标签
标签名:img
属性:
src:设置图片资源的路径
- 本地图片:将图片资源复制到img目录下
- 网络图片:将图片的网络地址作为src的属性值
width:宽度
height:高度
注:只设置宽(高),图片会等比例缩放
title:鼠标悬停在图片上显示的提示性文字
alt:图片无法加载时的提示性文字
1.7.4 超链接
标签名:a
属性:
- href:用于指定跳转的目标地址
- target:设置新页面的打开方式,取值:_blank、_self
<a href="http://www.qq.com" target="_self">QQ</a>
注:跳转到外部资源时需要加上 http://
超链接在页面内容进行跳转:
步骤:
- 在目标位置的标签上添加 id 属性,id 属性是标签的唯一标识
- 在起始位置的超链接标签中的 href 属性中通过 #id 值跳转
注:返回顶部
<a href="#">回到顶部</a>
1.7.5 列表标签
标签名:ol(有序列表)、ul(无序列表)
列表项:li
属性:
- type:
- 在无序列表中,取值:disc 默认的实心圆、square 方形、circle 空心圆
- 在有序列表中,取值:a、A、i、I、1
- start:在有序列表中,用于明确列表项的序号从几开始
列表嵌套:将一个列表作为另一个列表的列表项中的内容
1.7.6 表格标签
标签名:table
表格行:tr
单元格:td
属性:
border:设置表格边框的粗细
注:一般使用 css 的写法,style=“border : 粗细 颜色 样式”
width:宽度
height:高度
cellspacing:单元格的间距
cellpadding:单元格的内边距,单元格中的内容与单元格之间的距离
align:水平方向,取值:left、right、center
valign:垂直方向,取值:top、bottom、center
不规则表格:
rowspan:跨行
colspan:跨列
1.7.7 标签的补充
- br:换行
- hr:水平线
- p:段落
- b:粗体
- i:斜体
- strong:粗体
- sub:下标
- sup:上标
span:没有默认样式效果的一种行内元素
div:没有默认样式效果的一种块级元素
1.8 表单标签
概念:用于获取用户的数据,提交到服务器
标签:form
属性:
- action:明确表单中的数据提交到哪里
- method:设置提交方式
- get:提交的数据会拼接在地址栏上,默认的提交方式
- post:提交的数据不会拼接在地址栏上
输入域标签:input
属性
type
取值如下:
- text:文本输入框,它是默认值,框中接收的数据类型是字符串
- password:密码框,框中接收的数据类型是字符串
- radio:单选框,互斥性
- checkbox:复选框
- button:普通按钮
- submit:提交按钮
- reset:重置按钮
1.8.1 文本框和密码框
属性:
- placeholder:提示性内容
- maxlength:最大字符数
- value:框中的内容,同时也是提交到服务器上的值
- name:用于定义当前input提交到服务器时,服务器获取数据所需要的键,它会和接收的数据形成键值对,以
“键=值&键=值“
的形式提交到服务器
1.8.2 单选框和复选框
属性:
- name:用于定义当前input提交到服务器时,服务器获取数据所需要的键,它会和接收的数据形成键值对,以
“键=值&键=值“
的形式提交到服务器;在多个单选框中使用,相同的name值还会将它们归纳在同一组中,使它们具有互斥性。- value:提交到服务器上的值
- checked:默认选中状态
- checked=“”
- checked=“checked”
- checked
注:可以使用label标签将单(复)选框与文字进行关联,达到点击文字就相当于点击标签的效果
<input type="radio" name="gender" value="3" id="wz"/><label for="wz">保密</label>
1.8.3 按钮
提交按钮 submit:将用户的数据提交到服务器
属性:
- value:用于设置按钮上的文字
注:button标签如果出现在表单,它就是一个提交按钮,如果不在表单中,就是一个普通按钮
重置按钮:reset:用于重置当前表单恢复默认状态
属性:
- value:用于设置按钮上的文字
普通按钮button
属性:
- value:用于设置按钮上的文字
二,css
2.1 概念
全称:Cascading Style Sheets,层叠样式表,级联样式表
文件名以.css结尾
2.2 作用
用于对页面进行样式的设置,美化页面
由于不同标签具有不同的样式属性,要记忆哪些标签有哪些样式属性非常繁琐,所以css的出现统一了所有标签所有样式的设置方式(语法、规则)
2.3 语法
内联样式:将样式以属性的形式写在开始标签中,只能影响当前标签
格式:
style="样式名1:样式值1;样式名2:样式值2;..."
内部样式表:可以影响到当前页面中所有指定标签
写法:
- 在 head 标签中,定义 style 标签
- 在 style 标签中,通过
选择器
找到标签,并对其进行样式的设置<style type="text/css"> 选择器{ 样式名1:样式值1; 样式名2:样式值2; ... } </style>
外部样式表:可以影响到所有引入了该样式文件的页面
写法:
- 创建 css 文件,在文件中定义样式,样式定义的方式与内部样式表相同
- 在需要引入该 css 文件的 head 标签中使用 link 标签的 href 属性来指定该 css 文件的路径
<link rel="stylesheet" type="text/css" href="css/test.css"/>
注释:/* 注释内容 */
2.4 选择器
概念:在 css 中,选择器是一种模式,用于选择需要添加样式的元素
2.4.1 元素选择器
根据元素名选择标签
格式:
元素名{ 样式 }
2.4.2 id选择器
根据唯一标识id属性选择标签
格式:
#id值{ 样式 }
2.4.3 类选择器
根据class属性选择标签
格式:
.class值{ 样式 }
2.4.4 通配符选择器
选择页面中所有标签
格式:
*{ 样式 }
2.4.5 子代、后代选择器
子代选择器:选择指定的子代元素
格式:
选择器1 > 选择器2{ 样式 }
后代选择器:选择指定的后代元素
格式:
选择器1 选择器2{ 样式 }
2.4.6 属性选择器
选择具有指定属性或者具有指定属性和属性值的元素
格式:
选择器[属性名]{ 样式 }
选择器[属性名=“属性值”]{ 样式 }
选择器[属性名=“属性值”][属性名=“属性值”]...{ 样式 }
注:属性选择器只会找那些写出了指定属性的元素,而不是具有指定属性的元素
2.4.7 nth-of-type
li:nth-of-type(2n+1){ background: red; } li:nth-of-type(2n+2){ background: blue; }
- 该写法只认 n 字母
- 前面的 xn 表示每隔 x - 1 个元素设置样式,如果 x 是负数,表示从后往前
- 后面的整数表示从第几个元素开始,可以是负数
- 每隔多少个元素,必须写在从第几个元素开始之前
2.4.8 优先级
- 内联样式的优先级最高
- 内部样式表和外部样式表,谁后写,优先级更高(就近原则)
同为内(外)部样式表,哪个选择器的范围更精准,优先级更高
强制的提升优先级:可以在样式后加上
!important
2.4.9 伪类选择器
格式:
选择器:伪类选择器{ 样式 }
伪类选择器:link、hover、active、visited
a:link{ color: pink; } a:visited{ color: green; } a:hover{ color: red; } a:active{ color: blue; }
注:要根据 link visited hover active(爱恨) 顺序
2.5 盒子模型(box model)
研究的是盒子中内容与盒子的间距,盒子与盒子之间的距离,以及边框
2.5.1 边框
格式:
border: 粗细 颜色 样式
注:
- 粗细:线框的高度(厚度)
- 颜色:颜色单词、rbg()、#xxxxxx
- 样式:solid实线、double双划线、dashed虚线、dotted点划线
- 粗细、颜色、样式的顺序是任意
- 边框可以进行单边设置
- 可以对某条边框的颜色、粗细、样式单独进行设置
div{ width: 400px; height: 400px; border-top: dashed 1px red; border-bottom: dotted 2px blue; border-left: orange solid 3px ; /*border-right: 4px yellow double;*/ border-right-color: green; border-right-style: double; border-right-width: 10px; }
圆角边框:
格式:
border-radius: n px;
注:
- 圆角边框的本质是用一个指定半径的圆去内切四个角
- 圆角可以单独设置
p{ width: 400px; height: 400px; border: 1px solid goldenrod; /*border-radius: 200px;*/ border-bottom-right-radius: 200px; border-top-left-radius: 200px; }
2.5.2 内边距
盒子中的内容到边框的距离,padding
使用方式:
- padding : n px, 四个方向
- padding-left/right/top/bottom : n px ,指定方向
- padding:a px b px ,上下内边距为apx,左右内边距为bpx
- padding:a px b px c px d px,上右下左内边距分别为a、b、c、dpx
注:设置内边距会影响整个盒子的大小
#inp1{ /*padding: 10px;*/ padding-left: 10px; padding-bottom: 20px; padding-top: 30px; } #sp{ border: 1px solid red; /*padding: 50px 10px;*/ padding: 0px 20px 80px 100px; }
2.5.3 外边距
盒子与盒子的间距,margin
使用方式:
- margin: n px, 四个方向
- margin-left/right/top/bottom : n px ,指定方向
- margin : a px b px ,上下外边距为apx,左右外边距为bpx
- margin : a px b px c px d px,上右下左外边距分别为a、b、c、dpx
#sp1{ /*margin-top: 20px; margin-left: 50px; margin-right: 50px;*/ /*margin: 50px;*/ /*margin: 10px 100px;*/ /*margin: 10px 30px 60px 120px;*/ }
注:上下外边距取较大者,左右外边距取两个外边距之和
2.6 定位
属性:position
取值:
- relative:相对定位,相对于默认位置进行移动
- absolute:绝对定位,相对于body进行位置的移动,绝对定位后,元素相当于使用了浮动
- static:默认定位
- fixed:固定定位,相对于body进行位置的移动,位置不会随着页面上下位置的变化而变化
注:position 只是确定了定位的方式,具有的位置要结合 left、right、top、bottom属性配合使用
/*#pp{ position: relative; top: 100px; }*/ #pp{ position: absolute; top: 0px; } #d{ width: 100px; height: 100px; border: 1px solid blue; position: fixed; right: 0; top: 600px; }
2.7 隐藏和显示
隐藏:
- visibility : hidden,仍然占据位置
- display : none,不占位置
对应的显示:
- visibility : visible
- display : inline(行内)/block(块级)
2.8 浮动
浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。
由于浮动框不在文档的普通流中,所以文档的普通流中的块框表现得就像浮动框不存在一样。
属性:float
取值:left、right
清除浮动:在浮动的元素前后加上 div 对该 div 添加样式 clear : both
三,js
3.1 概念
全称:JavaScript
1995年,由网景和SUN公司开发的
它是基于对象和事件驱动的脚本语言
基于对象:js 中提供了很多对象,可以直接调用
事件驱动:js 中提供了很多动态效果的实现
作用:提供了用户的体验,提供了交互效果
3.2 特点
- 由浏览器解释运行
- 提供了动态的交互效果
- 不能直接访问磁盘
- js 是弱类型的语言:变量的类型由值决定
3.3 js 与 html 的结合
内嵌式
在 head 标签中,使用 script 标签,在 script 标签中编写 js 代码
外联式
创建 js 文件,在 js 文件中编写 js 代码,在 head 标签中使用 script 标签的 src 属性引入该 js 文件
注:
- 两种方式可以同时使用,如果方法名相同,那么只执行后写的
- 两种方式不要混用
3.4 注释
- 单行注释:// 注释内容
- 多行注释:/* 注释内容 */
3.5 js 的语法
3.5.1 变量
变量的定义:使用关键字 var
定义时赋值
var name = 'zs'; var age = 20;
先定义后赋值
var height; height = 140;
注:
- 关键字 var
- 变量的类型由值决定
- 在函数中定义变量时,如果省略了 var,那么这个变量就是一个全局变量
命名规则:
- 可以由数字、字母、下划线、美元符组成
- 数字不能开头
- 不能与关键字重名
- 区别大小写
命名规范:
- 使用驼峰命名
- 见名知意
3.5.2 数据类型
一般的数据类型:
- Boolean:布尔类型,只有true和false
- Number:数值类型,表示所有整数和小数
- String:字符串类型,用一对单引号或者双引号括起来
- Object:对象
- Array:数组
- RegExp:regex regexp
- Function:方法
特殊的数据类型:
- Null:只有一个值 null,当定义了一个变量后,这个变量没有任何指向,它就是 null
- Undefined:只有一个 undefined,当定义了一个变量后,没有赋值,那么它就是 undefined
- NaN:not a number,不是一个数字
3.5.3 运算符
算术运算符
+ - * / % ++ --
逻辑运算符
&& || ! ^
关系运算符
> < >= <= != ==:判断数据的值是否相等 ===:判断数据的值和类型是否都相等
赋值运算符
+= -= *= /= %= =
三元运算符
条件表达式 ? 表达式1 : 表达式2
typeof 运算符
typeof(参数)
返回参数的数据类型
字符串类型返回 string
数值类型返回 number
布尔类型返回 boolean
方法返回 function
其他返回 object
3.6.4 流程控制
选择结构:
if(条件){ }
if(条件){ } else{ }
if(条件1){ } else if(条件2){ } ... else{ }
分支结构:
switch(表达式){ case 值1: break; case 值2: break; case 值3: break; ... default: break; }
注:表达式除了Java中可以放置的类型之外,还可以是布尔类型和小数
循环结构:
while(条件){ }
do{ }while(条件);
for(初始化;条件;步进表达式){ }
3.6.5 函数
函数的定义:
function 函数名(参数列表){ 方法体 }
function:函数必须通过
function
关键字来定义返回值:js 中的方法没有返回类型,但是可以通过
return
来返回结果参数列表:与 Java 一样,定义时叫形参,调用时叫实参。
注:js 函数中的形参不需要写类型也不能写 var
参数的问题:
- 参数的使用方式与Java相同
- 形参不能写var
- 当出现同名方法时,后定义的方法会覆盖先定义的方法
- 调用函数时,参数可以使用this,哪个标签触发了事件,this就表示哪个标签对象
3.6.6 匿名函数
格式:
function (参数列表){ 方法体 }
匿名函数的调用:
用一个变量来接收匿名函数,本质上这个变量名就是函数名
var f = function (a){ return a+a; }
通过事件调用
onload = function(){ var img = document.getElementById("img"); var count = 0; img.onmousemove = function(){ count++; this.src = "../img/"+ count%3 +".jpg"; } }
3.6.7 数组
js 中同一个数组中元素的类型可以是任意的;并且数组的长度是可变的
数组的创建:
var arr = new Array(元素1,元素2,元素3,...);
var arr = new Array(数组的长度);
var arr = [元素1,元素2,元素3,...];
数组元素的增删改查:
- 增:
- push(元素1,元素2,元素3,…)
- unshift(元素1,元素2,元素3,…)
- splice(下标,0,元素):向指定位置插入元素
- 删:
- splice(下标,数量):从指定位置删除指定的个数
- 改:
- splice(下标,数量,元素):将元素替换从指定下标开始指定数量个元素
- 查:
- length
- 数组名[下标]
3.7 全局函数
概念:全局函数是每一个js中本身就带有的函数,不需要创建对象,直接调用即可
- parseInt()
- parseFloat()
- isNaN()
- eval()
- encodeURI()
- decodeURI()
3.8 正则表达式
创建:
var reg = new RegExp("正则表达式");
var reg = /正则表达式/;
方法:
- 正则表达式对象.test(要匹配的字符串)
注:
- 使用test判断字符串是否匹配,只要字符串中的部分内容符合正则就会返回true,因此需要使用
^
和$
来控制正则表达式的开头和结尾
3.9 事件监听
某个元素被执行了某些操作后,触发了某种功能的执行
3.9.1 点击事件
onclick:单击事件
ondblclick:双击事件
onload = function(){ var start = document.getElementById("start"); var end = document.getElementById("end"); var time = document.getElementById("time"); var count = 0; start.ondblclick = function(){ t = setInterval(function(){ count++; var hour = parseInt(count / 3600); var minute = parseInt((count - hour * 3600) / 60); var second = count % 60; time.innerText = (hour > 9 ? hour : "0"+hour)+":"+ (minute > 9 ? minute : "0"+minute)+":"+(second > 9 ? second : "0"+second); },100); } end.ondblclick = function(){ clearInterval(t); } }
<span id="time">00:00:00</span> <button id="start">开始</button> <button id="end">停止</button>
3.9.2 鼠标事件
onmouseover:鼠标移到元素上
onmousemove:鼠标在元素上移动
onmouseleave:鼠标从元素上移走
onload = function(){ var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ lis[i].style.height = "50px"; if(i % 2 == 0){ lis[i].style.background = "red"; }else{ lis[i].style.background = "black"; } lis[i].onmouseover = function(){ this.setAttribute("bgc",this.style.backgroundColor); this.style.background = "pink"; } lis[i].onmouseleave = function(){ this.style.background = this.getAttribute("bgc"); } } }
<ul> <li></li> <li></li> <li></li> <li></li> <li></li> </ul>
3.9.3 键盘事件
onkeydown
onkeyup
onload = function(){ var inp = document.getElementById("inp"); var btn = document.getElementById("btn"); inp.onkeyup = function(){ var val = this.value; var reg = /^1[0-9]{10}$/; if(reg.test(val)){ btn.disabled = false; }else{ btn.disabled = true; } } }
<input type="text" id="inp"/> <input type="button" id="btn" value="按钮" disabled/>
3.9.4 焦点事件
onfocus
onblur
function check(obj){ var sp = document.getElementById("sp"); var val = obj.value; var reg = new RegExp("^1[0-9]{10}$"); if(val.length == 0){ sp.innerText = "手机号不能为空!"; sp.style.color = "red"; obj.style.border = "1px solid red"; return; } if(!reg.test(val)){ sp.innerText = "手机号格式有误!"; sp.style.color = "red"; obj.style.border = "1px solid red"; return; } sp.innerText = "手机号可以使用!"; sp.style.color = "green"; obj.style.border = "1px solid green"; } function f(obj){ var sp = document.getElementById("sp"); sp.innerText = ""; obj.style.border = "1px solid black"; }
<input id="inp" type="text" onfocus="f(this)" onblur="check(this)" style="border:1px solid black;outline: none;"/> <span id="sp"></span>
3.9.5 表单事件
onsubmit()
function check(){ var username = document.getElementById("user").value; var password = document.getElementById("pwd").value; if(username.length == 0 || password.length == 0){ alert("用户名和密码不能为空"); return false; }else{ return true; } }
<form action="http://www.baidu.com" onsubmit="return check()"> <input type="submit"/> </form>
3.10 DOM
Document Object Model:文档对象模型
获取 document 对象:
- 直接使用:document
- 使用 window 对象调用:window.document
功能:
- 获取节点对象
- getElementById()
- getElementsByClassName()
- getElementsByTagName()
- getElementsByName()
- 创建节点对象
- createElement(标签名)
- createTextNode(文本)
- 添加节点对象
- appendChild()
- insertBefore(新节点,指定节点)
- 删除节点对象
- removeChild()
- remove()
3.11 BOM
Browser Object Model,浏览器对象模型
3.11.1 window
获取方式:可以直接使用 window 对象调用功能,并且 window 可以省略
常见方法:
- window.onload:页面加载事件,在整个页面加载完毕后执行
- window.alert():显示带有一段文本内容和确认按钮的警告框
- window.confirm():显示带有一段文本内容、一个确认按钮和一个取消按钮的确认框;点击“确认”返回true,反之,返回false
- window.prompt():显示带有一个文本输入框、一段文本内容、一个确认按钮和一个取消按钮的对话框;点击“确认”返回输入的内容,反之,返回null
- window.setTimeout(function f,毫秒值):在指定时间到达时执行一次函数
- window.setInterval(function f,毫秒值):每隔一段指定的时间执行一次函数
- window.clearTimeout(定时器id):删除指定的定时器
- window.clearInterval(定时器id):删除指定的定时器
3.11.2 history
获取方法:window.history
常用方法:
- back():后退一个页面
- forward():前进一个页面
- go(参数):
- 参数是正数n:表示前进n个页面
- 参数是负数n:表示后退n个页面
3.11.3 location
获取方式:window.location
常用方法:reload():重新加载页面
属性:href,指定跳转到的目标地址
20_servlet
一,服务器
1.1 web 相关的概念
软件的架构:
- B/S:浏览器、服务器
- C/S:客户端、服务器
B/S和C/S的区别:
- C/S占用的带宽更小
- B/S占用的硬盘更小
- C/S的页面更加美观
- B/S更加易于维护
网络通信的三要素:
- 传输协议 http 超文本传输协议
- ip地址
- 端口号
1.2 web服务器
服务器:安装了服务器软件的计算机
web服务器软件:
作用:接收用户的请求,做出响应
常见的 web 服务器:
- tomcat:Apache的中小型JavaEE服务器,开源的,免费的
- WebLogic:Oracle公司的大型JavaEE服务器
- WebSphere:IBM公司的大型JavaEE服务器
tomcat:
下载地址:tomcat.apache.org
安装:解压到不含中文的路径下
目录结构:
- bin:包含可执行的文件,里面有 startup.bat、shutdown.bat
- conf:包含配置,里面有 web.xml、server.xml
- lib:包含tomcat所依赖的jar包,以及Java代码编写web服务器时所需要的常用jar包
- logs:包含日志文件
- temp:包含临时文件
- webapps:包含web工程
- work:包含运行时产生的文件,例如:jsp编译后的文件
启动服务器
运行bin目录下的startup.bat来启动服务器
报错:
原因:
重复启动了,解决办法就是关闭原先的
端口号冲突,解决办法
在 cmd 中输入 netstat -no 找到占用 tomcat 端口应用的pid,在任务管理器中结束它
一闪:
原因:没有正确配置Java的环境变量
解决:去正确配置的配置
关闭服务器
- 点 X ,关闭窗口
- 运行 shutdown.bat
- ctrl + c
访问服务器
在浏览器中输入:http://localhost:8080/
注:如果在 server.xml 中将默认端口号从8080改成80,那么访问时可以省略端口号
1.3 在idea中配置tomcat
1.3.1 在idea中创建maven项目
1.3.2 同步依赖
- import changes:在pom.xml文件中添加依赖后,仍需要手动刷新
- enable auto_import:在pom.xml文件中添加依赖后,依赖会自动导入
注:
需要在pom.xml中配置
<groupId>com.qf</groupId> <artifactId>day28_02_servlet01</artifactId> <version>1.0-SNAPSHOT</version> <!-- 打包方式 --> <packaging>war</packaging> <!-- 1.JavaSE项目:jar 2.JavaEE项目:war 3.聚合工程:pom -->
1.3.3 maven项目的结构
- src/main/java:存放源代码
- src/main/resouces:存放配置文件
- src/test/java:存放测试代码
- pom.xml:project object model,它是maven项目的核心文件,定义了项目的构建方式,声明依赖
1.3.4 在maven项目中搭建tomcat
web.xml的基本配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> </web-app>
二,Servlet
2.1 概念和基本使用
概念:Servlet 全称 Server Applet,运行在服务器端的程序
Servlet 的本质是一个接口,它定义了Java类能被服务器访问到的规则
步骤:
- 定义类,实现Servlet接口
- 重写方法
- 在web.xml中注册Servlet
<servlet> <!-- Servlet名,作用是确定访问了指定的资源后,由哪个Servlet类来处理 --> <servlet-name>FirstServlet</servlet-name> <!-- 在确定了访问的指定资源后,由这个Servlet类来处理(全限定类名) --> <servlet-class>com.qf.servlet.FirstServlet</servlet-class> </servlet> <servlet-mapping> <!-- Servlet名,作用是确定访问了指定的资源后,由哪个Servlet类来处理 --> <servlet-name>FirstServlet</servlet-name> <!-- Servlet类的路径 --> <url-pattern>/first</url-pattern> </servlet-mapping>
2.2 Servlet 的执行原理
- 当服务器接收到了客户端的请求时,tomcat服务器会解析请求的地址
- 在 web.xml 中进行查找,是否有某个 url-pattern 标签体中的内容与之匹配
- 如果没有,会发生404(资源找不到)
- 如果有,会继续查找是否有一个 servlet 标签下的 servlet-name 中的值与该 url-pattern 所在 servlet 标签下的 servlet-name 中的值相同
- 如果没有,eclipse 发生服务器启动失败,idea 报错
- 如果有,就找到这个 servlet 标签下的 servlet-class 指定的类,执行该类
2.3 Servlet 的生命周期
init():初始化Servlet,默认第一次访问时执行,只执行一次
可以通过
<load-on-startup>n</load-on-startup>
n:
负数,默认,表示该Servlet的init方法在第一次访问时执行
0或者正数,表示该Servlet的init方法在服务器启动时执行,数字越小优先级越高
service():提供服务,每次访问时都会执行,该方法中提供了tomcat服务器封装好了的请求和响应对象
destroy():销毁,只有在正常关闭服务器时才会执行
2.4 Servlet的注解配置
可以直接在 Servlet 类上通过:
@WebServlet("/t") public class ThirdServlet implements Servlet{ ... }
的方式来注册Servlet,此时就相当于配置了 ThirdServlet 访问的路径是 “t”,
注:servlet 的版本至少是 3 以上
2.5 Servlet 的体系结构
HttpServlet -> GenericServlet -> Servlet
HttpServlet:专门针对http协议的一种Servlet接口的封装,它简化了Servlet中的实现步骤,并且提供了doGet()/doPost()方法。
步骤:
- 定义类,继承HttpServlet
- 重写 doGet()、doPost() 方法
注:如果指定的提交方式没有对应的 doXxx() 方法,则会发生405
2.6 url-pattern 的配置
完全匹配:/xxx
例如:url-pattern 配置的值是 /t,那么访问时必须访问 /t
扩展名匹配:*.xxx
例如:url-pattern 配置的值是 *.jsp,那么访问所有以 jsp 为后缀的文件时,都由指定的Servlet来处理
目录匹配:/xxx/*
例如:url-pattern 配置的值是/abc/*,那么访问所有abc目录下的资源时,都由指定的Servlet来处理
匹配全部:/*
如果访问的路径是Servlet,并且该Servlet有自己的url配置,则访问该Servlet;除此以外都由配置了/* 的Servlet来处理。
注:目录匹配和扩展名匹配不能混合用
2.7 欢迎页面(首页)的配置
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
作用:配置默认的首页,在访问服务器首页时,可以不写出首页的资源名就直接访问到首页
注:
- 在中,系统会从上到下依次查找是否有该页面,如果找到了就将它作为首页,不再继续查找,如果没有找到就报404
- 如果自己的工程中,没有配置,那么就会根据tomcat默认配置文件中的来设置首页
- 一旦自己的工程中,配置了,那么tomcat默认配置文件中的就失效了
三,请求消息数据的格式
请求行:
- 请求方式:post、get
- 请求的地址:http://localhost/test ,如果是以get方法提交,请求的数据会拼接在地址后,http://localhost/test?username=xxx
请求头:由多个键值对组成,
每个键值对的格式:
请求头的名称:请求头的值
请求头是客户端在向服务器发起请求时,告知服务器的相关信息
常见的请求头:
- user-agent:告知服务器,当前浏览器的版本信息,用于解决浏览器的兼容性问题
- referer:告知服务器,当前的请求是从哪一个页面过来的,常用于防盗链
- cookie
请求体:封装了以post方式提交的请求数据
四,响应消息数据的格式
响应行:
- 组成:协议/版本,响应状态码
- 响应状态码:服务器告知浏览器本次请求和响应的状态
- 状态码都是3位数字
- 分类:
- 1xx:服务器接收到了客户端的消息,但是消息还没有处理完
- 2xx:表示成功
- 3xx:表示重定向,302重定向、304访问缓存
- 4xx:客户端错误,404找不到资源、405提交方式没有对应的doXxx()方法
- 5xx:服务器错误,500服务器内部发生异常
响应头:由多个键值对组成,
每个键值对的格式:
响应头的名称:响应头的值
服务器告知客户端的信息
常见的响应头:
content-type:服务器告知客户端以何种编码格式解析数据
content-disposition attachment;filename=文件:告知客户端文件的打开方式是以附件形式打开
响应体:服务器传输给客户端的数据
五,HttpServletRequest
5.1 获取请求消息
获取请求行相关
- getMethod():获取请求方式:post、get
- *getContextPath():获取虚拟路径
- getServletPath():获取请求的Servlet
- getQueryString():获取以get方式提交的数据
- getProtocol():获取协议/版本:http/1.1
- *getRequestURI()
- *getRequestURL()
注:
- uri:统一资源标识符,获取项目在服务器中根目录的相对路径
- url:统一资源定位符,从服务器的位置开始获取路径
- 获取请求头相关
- getHeader(String name):通过请求头的名称获取对应的值
- getHeaderNames():获取所有请求头的名称
获取请求体相关
- getReader():获取字符输入流
- getInputStream():获取字节输入流
注:
- 只能获取以post方式提交的数据
- 一次请求中只能调用一次获取输入流的方法
5.2 获取请求参数的通用方法
- getParameter(String name)
- getParameterValues(String name)
- getParameterNames()
- getParameterMap()
注:中文乱码问题
原因:tomcat 服务器的默认编码格式是iso8859-1,这种编码格式不支持中文,所以提交中文时会出现乱码
get
// tomcat8及以上版本不会出现中文乱码, // 如果是7版本,需要通过如下写法: String name = req.getParameter("name"); name = new String(name.getBytes("iso8859-1"),"utf-8");
post
// 在接收到数据之前, req.setCharacterEncoding("utf-8");
5.3 请求转发
请求转发是一种在服务器内部实现资源跳转的方式
步骤:
通过 request 对象获取请求转发器,同时指定要跳转到的目标地址
执行转发
RequestDispatcher dispatcher = req.getRequestDispatcher("/f2"); dispatcher.forward(req,resp);
注:使用请求转发实现跳转,地址栏不变,本质上请求转发只执行了一次请求
5.4 共享数据
域对象:一个有作用范围的对象,可以在指定的范围内实现数据的共享
pageContext:只能在当前资源中共享数据
*request:能在一次请求中共享数据
session:能在一次会话中共享数据
servletContext:能在整个web应用中共享数据
域对象的通用方法:
- setAttribute(String name,Object o)
- getAttribute(String name)
- removeAttribute(String name)
5.5 获取ServletContext
ServletContext:它是整个web应用的对象,每个引用有且只有一个ServletContext。
它也是一个域对象,能在整个web应用中共享数据
获取方式:
req.getServletContext(); this.getServletContext();
六,ServletContext
6.1 获取全局初始化参数
步骤:
配置web.xml
<context-param> <param-name>name</param-name> <param-value>张三</param-value> </context-param>
获取
getInitParameter(String name); getInitParameterNames();
6.2 共享数据
域对象的通用方法:
- setAttribute(String name,Object o)
- getAttribute(String name)
- removeAttribute(String name)
6.3 获取服务器中资源的绝对路径
方法:getRealPath(String path):通过相对路径获取资源在服务器中的绝对路径
七,HttpServletResponse
7.1 设置响应行
方法:
resp.setStatus(int code);
7.2 设置响应头
方法:
resp.setHeader(String name,String value);
重定向:是一种实现资源跳转的方式
resp.setStatus(302); resp.setHeader("location","/r2"); // 简化写法 resp.sendRedirect("/r2");
修改编码格式:
resp.setHeader("content-type","text/html;charset=utf-8"); // 简化写法 resp.setContentType("text/html;charset=utf-8");
7.3 设置响应体
- getWriter()
- getOutputStream()
注:一次响应中只能调用一次获取输出流的方法
7.4 请求转发和重定向
请求转发的特点:
- 地址栏不变
- 只执行了一次请求
- 不能访问其他服务器中的资源
重定向的特点:
- 地址栏变化
- 执行了两次请求
- 可以访问其他服务器中的资源
区别:
- 请求次数
- 地址栏
- 访问其他服务器
- 请求转发的性能优于重定向
注:多次请求转发或者重定向,只会执行第一次的请求转发或者重定向
21_会话技术
会话技术
概念:会话是客户端与服务器之间的一个通信过程,一次会话中可以包含多次请求和响应
一次完整的会话:客户端第一次请求服务器时建立会话,直到任何一方断开为止
作用:在一次会话的范围内的多次请求之间实现数据的共享
分类:
- 客户端会话技术:Cookie
- 服务器会话技术:Session
一,Cookie
概念:客户端会话技术,它将数据存储到客户端(浏览器)中
使用 Cookie 的相关方法:
- Cookie c = new Cookie(String name,String value);创建 Cookie 对象,将两个参数形成的键值对绑定到该 Cookie 对象中
- resp.addCookie(Cookie c);发送 Cookie 到客户端,将Cookie对象中的两个参数形成的键值对,在响应头中发送给客户端,以
Set-Cooike:name=value
的形式出现在响应头中- req.getCookies():返回请求头中每一个Cookie对象组成的数组,在请求头以
Cookie:name=value
的形式出现
Cookie的原理:
- 在客户端第一次访问服务器时,服务器会将Cookie对象中的键值对,以
Set-Cooike:name=value
的形式通过响应头发送给客户端- 在会话没有结束的情况下,客户端在访问服务器的任何资源时,都会在请求头中以
Cookie:name=value
的形式将Cookie提交给服务器
Cookie的细节问题:
是否可以一次发送多个Cookie对象?
可以的;如果键相同,那么原来的值会被覆盖
是否可以存储中文在Cookie中?
tomcat7,存储中文时会报错,tomcat8,会发生乱码,
需要进行编码的修改:
String sex = "女"; sex = URLEncoder.encode(sex,"utf-8"); Cookie c4 = new Cookie("sex",sex);
服务器获取后的数据是编码后的内容,需要进行解码:
Cookie[] cookies = req.getCookies(); if(cookies != null){ for(Cookie c : cookies){ if(c.getName().equals("sex")){ String s = c.getValue(); System.out.println(s); s = URLDecoder.decode(s,"utf-8"); System.out.println(s); } } }
Cookie 在客户端中可以保存多久?
- 默认情况:Cookie是会话级别的,浏览器关闭后,Cookie被销毁
- 持久化:cookie.setMaxAge(int seconds)
- 正数:持久化,时间从发送Cookie开始计时
- 负数:默认值,浏览器关闭后,Cookie被销毁
- 0:立即删除Cookie
Cookie的作用范围?也就是说在访问哪些资源时会携带者Cookie?
- 默认情况:访问当前项目的任何资源时都会携带Cookie
- 可以设置携带路径:setPath(String path)
- setPath(“/”)
- setPath(“/虚拟路径”)
- setPath(“/虚拟路径/目录名”)
- setPath(“/虚拟路径/目录名/…/文件名”)
Cookie的特点:
- Cookie将数据存储到客户端中
- Cookie存储的数据不安全,Cookie可能被销毁(关闭浏览器、到达最大时间、手动清除)
- 浏览器对单个Cookie的大小有限制(4K),并且浏览器对同一域名下的Cookie数量也有限制
二,Session
概念:Session 是服务器会话技术,能够在一次会话中的多次请求之间共享数据,它将数据存储在服务器中
HttpSession 是一个接口,它也是一个域对象:
使用 Session 的相关方法:
- Session 是一个域对象
- setAttribute(String name,Object o)
- getAttribute(String name)
- removeAttribute(String name)
- Session 对象的创建和获取:req.getSession()
Session 的原理:
- Session 的实现是依赖于 Cookie 的
- 当调用 req.getSession() 时,服务器会看客户端的请求头中是否携带存有指定JSESSIONID的 Cookie,如果有,则在服务器中获取该JSESSIONID 所对应的 Session 对象,如果没有,则创建一个新的 Session 对象
- 当客户端关闭后,无法再从Session中获取存储的数据,因为JSESSSIONID所在的Cookie默认是会话级别的,随着客户端的关闭而释放;如果想要在关闭客户端后再次打开后还能获取到原先的Session,需要对存有该Session的JSESSIONID的Cookie进行持久化
Session 的细节问题:
客户端关闭,服务器不关,是否还能从Session中获取数据?
默认情况,不能
可以对 Session 进行持久化,对存有JSESSION和Session id的Cookie进行持久化
Cookie c = new Cookie("JSESSIONID",session.getId()); c.setMaxAge(60); resp.addCookie(c);
服务器关闭,是否还能从Session中获取数据?
Session 何时被销毁?
在tomcat\conf中,有一个web.xml的配置文件,默认配置 Session 的失效时长是30分钟,也可以在项目中的web.xml中手动配置
<session-config> <session-timeout>1</session-timeout> </session-config>
session.invalidate();
Session 的特点:
- Session 存储在服务器中
- Session 是一个域对象可以存储任意类型
- 存储数据的大小没有限制
Session 和 Cookie 的区别:
- 存储数据的位置:Session 在服务器中,Cookie 在客户端中
- 存储数据的大小:Session 没有限制,Cookie 有限制
- 安全性:Session 安全,Cookie 不安全
22_过滤器Filter
一,过滤器 Filter
1.1 概念
生活中的过滤器:纱窗、净水器
web中过滤器:当访问服务器的某些资源时,过滤器可以先把请求拦截下来,完成一些特殊的功能
过滤器的作用:统一编码格式、登录验证
1.2 使用步骤
定义类实现 Filter 接口
重写方法
在 web.xml 中配置
<filter> <filter-name>TestFilter1</filter-name> <filter-class>com.qf.filter.TestFilter1</filter-class> </filter> <filter-mapping> <filter-name>TestFilter1</filter-name> <url-pattern>/t1</url-pattern> </filter-mapping>
1.3 过滤器的执行流程
- 启动服务器,执行init()方法初始化过滤器
- 当访问了配置的拦截路径时,该访问动作被过滤器拦截下来,执行doFilter()方法
- 执行功能的增强
- 执行放行:chain.doFilter(req,resp);
- 访问到指定的资源
- 执行放行后的语句
过滤链:
一个资源被多个过滤器过滤器
执行顺序:
- 过滤器1
- 过滤器2
- 放行后访问目标资源
- 过滤器2
- 过滤器1
1.4 过滤路径的配置
过滤指定的资源:/xxx
例如:/abc,表示访问/abc时会先被过滤器拦截
过滤指定的目录:/目录名/*
例如:/abc/*,表示访问abc目录下的任何资源时都会先被过滤器拦截
过滤指定扩展名的资源:*.扩展名
例如:*.jsp,表示访问项目中的所有jsp资源时都会先被过滤器拦截
过滤所有资源:/*
23_JSP
一,jsp
1.1 概念
jsp:java server pages,java 服务器页面
jsp 是一种特殊的可以在html页面中嵌入java代码的页面,以.jsp为后缀
作用:简化 java 代码的书写
jsp 的本质是一个Servlet,这个jsp在第一次被访问时编译运行
例如:test.jsp -> test_jsp.java -> test_jsp.class
test.jsp 翻译成了 test_jsp 类,这个类继承了 HttpJspBase,而 HttpJspBase 继承了 HttpServlet
1.2 jsp 语法
jsp 中定义java代码的格式:
<%= java代码 %>:java代码在service()方法的out.print()方法的参数中
<% java代码 %>:java代码在service()方法中
<%! java代码 %>:java代码在jsp转换成servlet的成员位置
1.3 内置对象
概念:在 jsp 中由tomcat服务器创建,不需要手动创建,可以直接调用的对象(9个)
- *request
- *response
- *session
- *application
- page
- *pageContext
- pageContext也是一个域对象,作用范围是当前页面
- 获取其他8个内置对象
- *out
- out.write():在响应输出内容前,输出的内容会先存储在out缓冲区中
- response.getWriter().write():在响应输出内容前,输出的内容会先存储在response缓冲区中
- response缓冲区优先于out缓冲区被访问,所以使用response进行输出的结果一定在out输出之前
- config
- exception:使用前提,在 page 指令中配置 isErrorPage=“true”
1.4 jsp 的指令
格式:
<%@ 指令名称 属性名1=属性值1 属性名2=属性值2 ... %>
1.4.1 page 指令
配置当前jsp页面
常用属性:
- language
- contentType:配置当前jsp页面响应给客户端时指定的编码格式
- import:导包
- session:配置当前jsp页面是否可以使用session内置对象,默认是true
- errorPage:用于配置如果当前页面发生了异常,跳转到哪个指定的页面
- isErrorPage:配置在发生异常后跳转到的页面,取值:1.true 2.false默认的,只有配置成了true,才可以在该页面中使用内置对象exception
- isELIgnored:配置当前的jsp页面中是否忽略所有EL表达式
- true:表示忽略整个页面所有EL表达式,EL表达式不会被解析,会按原样输出
- false:默认值,任何一个jsp都有解析el的引擎,不会忽略
1.4.2 include 指令
在当前页面中嵌入其他的页面
<%@ include file="路径">
1.4.3 taglib 指令
用于引入标签库
jstl:jsp standard tag library,jsp 标准标签库
二,EL 表达式
2.1 概念
EL:Expression Language,表达式语言
作用:简化 jsp 中的 java 代码,用于获取数据
语法格式:
${ 表达式 }
注:
jsp 默认支持 EL
忽略 EL 表达式
忽略页面中所有EL表达式:在jsp在page指令中配置isELIgnored=“true”
忽略指定的一条EL
\${ 表达式 }
EL 表达式不显示 null
2.2 EL的运算符
.
:访问调用属性和方法
[]
:访问集合
+、-、*、/、%
:除可以使用div
、取模可以使用mod
==(eq)、!=(ne)、>(gt)、<(lt)、>=(ge)、<=(le)
&&(and)、||(or)、!(not)
empty
:判断容器对象是否为空或者长度是否为0
2.3 EL 的隐式对象
pageScope
requestScore
sessionScope
applicationScope
param
paramValues
header
headerValues
initParam
cookie
pageContext
2.3.1 获取域对象中的数据
语法格式:
${ 域对象的名称.键 }
域对象:
- pageScope
- requestScore
- sessionScope
- applicationScope
注:在获取域对象中的值时,可以简化成${键}
,会根据从小到大的范围查找,一旦找到就返回结果不再继续查找,pageContext 、request 、session、application
获取域对象中的对象
获取域对象中的对象以及属性
${ 域对象的名称.键 }:获取对象 ${ 域对象的名称.键.属性名 }:获取对象的属性值
根据对象的名称获取对象的属性值本质上是在调用对象的getXxx()方法,getXxx()必须有返回值,如果没有返回值会发生PropertyNotFoundException
获取List和数组中的对象
${ 域对象的名称.键 } ${ 域对象的名称.键[索引] } ${ 域对象的名称.键[索引].属性名 }
获取Map中的对象
${ 域对象的名称.键 } ${ 域对象的名称.键["Map中的键"] } / ${ 域对象的名称.键.Map中的键 } ${ 域对象的名称.键["Map中的键"].属性名 } / ${ 域对象的名称.键.Map中的键.属性名 }
2.3.2 获取请求的参数
- param
- paramValues
语法格式:
${ param.键} ${ paramValues.键}
2.3.3 获取请求头中的参数
- header
- headerValues
语法格式:
${ header.键} ${ headerValues.键}
2.3.4 获取全局初始化参数
- initParam
语法格式:
${ initParam.键}
2.3.5 获取应用的上下文(虚拟路径)
${ pageContext.request.contextPath}:动态获取应用的虚拟路径
2.3.6 获取Cookie
${ cookie.键 }:获取Cookie对象 ${ cookie.键.value}:获取Cookie对象值
三,JSTL
3.1 概念
jstl:jsp standard tag library,jsp标准标签库
它是由Apache组织提供的开源免费的jsp标签库
作用:简化 jsp 中的 java 代码
要使用jstl,必须先引入jstl的相关依赖:
<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>
还需要在taglib指令中,配置:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
3.2 if 标签
属性:
- test:接收条件表达式,必需的属性
- true:执行if标签体中的代码
- false:不执行if标签体中的代码
注:c:if标签没有对应的else标签。如果需要有对立的else情况,需要重新定义一个c:if
<c:if test="${ empty user }"> <a href="login.jsp">登录</a> <a>注册</a> </c:if> <c:if test="${ ! empty user }"> 欢迎您:<a href="/update.jsp">${ user.username }</a> <a href="/exit">退出</a> </c:if>
3.3 choose 标签
choose->switch when->case otherwise->default
when的属性:test
- true:执行when标签体中的内容
- false:不执行when标签体中的内容
<c:choose> <c:when test="${ score >= 90 }">a</c:when> <c:when test="${ score >= 80 && score <= 89 }">b</c:when> <c:when test="${ score >= 70 && score <= 79 }">c</c:when> <c:when test="${ score >= 60 && score <= 69 }">d</c:when> <c:otherwise>e</c:otherwise> </c:choose>
3.4 forEach 标签
- 普通for循环
- 属性:
- begin:起始值
- end:结束值
- step:步长
- var:临时变量
增强for循环
属性:
- items:容器
- var:临时变量
- varStatus:循环状态对象
- count
- index
代码
<% Stu s1 = new Stu("张三",20,"女",88); Stu s2 = new Stu("李四",21,"男",18); Stu s3 = new Stu("王五",20,"女",72); Stu s4 = new Stu("赵六",23,"女",31); Stu s5 = new Stu("聂风",44,"男",100); ArrayList<Stu> stus = new ArrayList<>(); stus.add(s1); stus.add(s2); stus.add(s3); stus.add(s4); stus.add(s5); session.setAttribute("stus",stus); %> <table> <tr> <td>姓名</td> <td>年龄</td> <td>性别</td> <td>成绩</td> </tr> <c:forEach items="${ stus}" var="u"> <tr> <td>${ u.name}</td> <td>${ u.age}</td> <td>${ u.sex}</td> <td>${ u.score}</td> </tr> </c:forEach> </table>