文末有Gitee链接,记得star哦
课程整体内容概述
第一部分:编程语言核心结构
主要知识点:变量、基本语法、分支、循环、数组、
第二部分:Java面向对象的核心逻辑
主要知识点:OOP、封装、继承、多态、接口、
第三部分:开发JavaSE高级应用程序
主要知识点:异常、集合、|℃、多线程、反射机制、网络编程、
第四部分:实训项目
项目一:家庭收支记账软件
项目二:客户信息管理软件
项目三:开发团队人员调度软件
附加项目一:银行业务管理软件
附件项目二:单机考试管理软件
项目一:讲完流程控制时,可以做。第二章结束
项目二:讲完面向对象(上)。即第四章
项目三:讲完异常处理,即第七章
附加项目一:讲完异常处理,即第七章
附加项目二:讲完IO流以后,即第11章
Java语言概述
1 基础常识
软件:即一系列按照特定顺序组织的计算机数据和指令的集合。分为:系统软件和应用软件。
系统软件:Windows,Linux,macOS,Android,Unix,ios
应用软件:word,ppt,画图板.....
人机交互方式:图形化界面 vs 命令行方式
应用程序 = 算法 + 数据结构
①常用DOS命令
dir:列出当前目录下的文件以及文件夹
md:创建目录
rd:删除目录
cd:进入指定目录cd.. :退回到上一级目录
cd\\:退回到根目录
del:删除文件
exit:退出dos命令行
补充:echOjavase > 1.doc
常用快捷键:
←→:移动光标
↑ ↓:调阅历史操作命令
DeIete和Backspace:删除字符
2 计算机语言的发展迭代史
第一代:机器语言
第二代:汇编语言
第三代:高级语言
面向过程:C
面向对象:Java,JS,Python
3 Java语言版本迭代概述
1991年Green项目,开发语言最初命名为Oak(橡树)
1994年,开发组意识到Oak非常适合于互联网
1996年,发布JDK1.0,约8.3万个网页应用Java技术来制作
1997年,发布JDK1.1,JavaOne会议召开,创当时全球同类会议规模之最
1998年,发布JDK1.2,同年发布企业平台J2EE
1999年,Java分成J2SE、J2EE和J2ME,JSP/ServIet技术诞生
2004年,发布里程碑式版本:JDK1.5,为突出此版本的重要性,更名为JDK5
2005年,J2SE->JavaSE,J2EE->JavaEE,J2ME->JavaME
2009年,Oracle公司收购SUN,交易价格74亿美元
2011年,发布JDK7.0
2014年,发布JDK8.0,是继JDK5.0以来变化最大的版本
2017年,发布JDK9.0,最大限度实现模块化
2018年3月,发布JDK10.0,版本号也称为18.3
2018年9月,发布JDK11.0,版本号也称为18.9
4 Java语言应用的领域:
Java Web开发:后台开发
大数据开发
Android应用程序开发:客户端开发
5 Java语言的特点
面向对象性
两个要素:类、对象
三个特征:封装、继承、多态
健壮性:(1)取除了C语言的指针
(2)自动垃圾回收机制(仍然会出现内存溢出、内存泄漏)
(3)跨平台性:write once ,run anywhere:一次编译,到处运行(基于JVM实现)
开发环境搭建
1 JDK、JRE、JVM的关系
2 JDK的下载、安装
下载:官网
安装:一直下一步傻瓜式安装(安装路线不能包含中文、空格)****
3 path环境变量的配置
-
为什么配置path环境变量?
path环境变量:windows操作系统执行命令时要搜寻的路径
为了再任意文件路径下可以运行Java的开发工具(javac.exe java.exe) -
如何配置?
第一个Java程序
1 开发体验--HelloWorld
2 编写
创建一个java源文件:HelloWorld
class HelloWorld{
public static void main(String[] args){
System.out.println(\"Hello,World\");
}
}
3 编译
javac HelloWorld
4 运行
java HelloWorld
5 总结第一个程序
在一个java源文件中可以有多个类,但只能有一个类被public修饰,并且public修饰的类的类名必须与文件名一致
程序的入口是main,main方法的格式是固定的。
每个执行语句都要以;(分号)结尾
注释与API文档等
1 注释
分类:
单行注释//
多行注释/**/
文档注释/** */
作用:
对代码进行解释说明
特点:
注释内容可以被JDK提供的工具javadoc所解析,生成一套以网页形式的该程序的说明文档。
多行注释不能嵌套使用
2 Java API文档
语言提供的类库成为API
API文档:对提供的类库如何使用给的一个说明书,也可以说是字典
3 良好的编程风格
常用的开发工具
1 文本编辑工具
- 记事本
- UltraEdit
- EditPlus(常用)
- TextPad
- NotePad(常用)
2 Java集成开发环境(IDE)
- Jbuilder
- NetBeans
- Eclipse(常用)
- MyEclipse
- IntelliJ IDEA(常用)
关键字与标识符
1 Java关键字的使用
定义:被Java赋予特殊意义,用作专门用途的字符串(单词)
特点:所有字母都是小写的
具体哪些关键字:
2 保留字
现Java版本尚未使用,但以后版本可能会作为关键字使用的。
具体哪些保留字:goto、const
注意:自己命名标识符时要避免使用这些保留字
3 标识符的使用
定义:需要自己起名字的地方都叫做标识符
涉及到的结构:
包名、类名、接口名、方法名、变量名、常量名
规则:(必须要遵守。否则,编译不通过)
由26个英文字母大小写,0-9 ,_或 $ 组成
数字不可以开头。
不可以使用关键字和保留字,但能包含关键字和保留字。
Java中严格区分大小写,长度无限制。
标识符不能包含空格。
规范:(可以不遵守,不影响编译和运行。但是要求大家遵守)
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个 单词首字母大写:xxxYyyZzz
常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
注意点: 见名知意
变量的使用 (重点)
1 变量的分类
1.1按数据类型分
详细说明:
整型:byte(一字节)、short(2字节)、int(4字节)、long(8字节)
①byte范围(-128-127)
②声明long变量时,必须以l或L结尾
③一般定义整型变量时,使用int型
④整型的常量,默认类型为:int型
浮点型:float(4字节)、double(8字节)
①浮点型,表示带小数点的数值
②float的范围比int还大
③声明float变量时,必须以f或F结尾
④定义浮点型变量时,一般使用double型
⑤浮点型的常量,默认类型为:double型
字符:char(1字符=2字节)
①定义char变量时,需要用一对\'\'包裹,并且只能含有一个字符
②表示方式:1.声明一个字符 2.转义字符 3.直接使用Unicode值来表示字符型常量
布尔型:boolean
①只有两个值:true、false
②常常在条件判断、循环结构语句中使用
- 按声明的位置分类(了解)
2 定义变量的格式
①数据类似 变量名 = 变量值;
②数据类型 变量名;
变量名 = 变量值;
3 变量使用的注意点
①变量必须先声明,后使用
②变量都定义在其作用域内。在作用域内,它是有效的
③同一个作用域内,不能声明两个同名变量
4 基本数据类型变量间运算规则
4.1 涉及到的基本数据类型:
byte、short、char、 int 、 long 、 float 、double
4.2 自动类型转换(只涉及7种基本数据类型)
byte,short,char --> int --> long --> float --> double
结论:当容量小的数据类型的变量与容量大的数据类型做运算时,结果会自动转换为容量大的数据类型
特别的:当byte、char、short三者相互或自己做运算时都会转换为int型
注:容量大小指的是,表示数的范围的大小。而不是字节大小。比如float容量要大于long
4.3 强制类型转换(只涉及7种基本数据类型)
自动类型转换的逆运算
①需要强转符()
②强制类型转换会出现精度损失
4.4 String与8种基本数据类型间的运算
①String属于引用型数据类型,理解为:字符串
②声明String类型变量时,需要用一对\"\"包裹。
③String可以和8种基本数据类型做运算,且运算只能是连接运算:+
④运算的结果仍为String类型
避免:
String s = 123;//编译错误
String s1 = \"123\";
String s2 = (int) s1;//编译错误
运算符
1 算术运算符 +(正) -(负) + - (前)++ (后)++ (前)-- (后)-- * / % +(连接符)
1.1 典型代码:
//除号: /
int num1 = 12;
int num2 = 5;
int num3 = num1 / num2;
System.out.println(num3);//结果num3 = 2;
//取余 :%
结果的符号与被模数的符号相同
开发中,通常使用%来实现能否被除尽
int y1 = 14;
int y2 = 3;
int y = y1 % y2;
System.out.println(y);//结果y = 2;
//注意点:
short s1 = 2;
//s1 = s1 + 1;//编译失败
//s1 = (short)(s1 + 1);//编译成功
s1++;//自增1不会改变本身的数据类型
System.out.println(\"s1 = \" + s1);
//问题:此时 bb1 等于多少?
byte bb1 = 127;
bb1++;
System.out.println(\"bb1 = \" + bb1);//bb1 = -128
1.2 特殊说明的
①:(前)++:先自增1;后运算
(后)++:先运算;后自增1②:(前)--:先自减1;后运算
(后)--:先运算;后自减1③连接符: + 只能用于String类型与其他数据类型之间使用。
2 赋值运算符 = += -= *= /= %=
2.1 典型代码:
//赋值符号: =
int i1 = 10;
boolean bl = true;
//连续赋值
int i2,i3;
i2 = i3 = i1;
int j1 = 10,j2 = 20;
int num = 100;
num += 2;
System.out.println(num);
short s1 = 100;
//s1 = s1 + 2;//编译不通过
s1 += 2;//不会改变变量本身的数据类型
System.out.println(s1);
2.2 特殊说明的
①结果不会改变变量本身的数据类型
②实现变量+2的操作,有几种方法?(前提是int num = 10;)
方式一:num = num + 2;
方式二:num += 2;//推荐
③实现变量+1的操作,有几种方法?(前提是int num = 10;)
方式一:num = num + 1;
方式二:num += 1;//推荐
方式三:num++;//推荐
3 比较运算符 == <= >= > < instanceof
3.1 典型代码:
int i = 10;
int j = 20;
System.out.println(i == j);
System.out.println(i = j);
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2);
System.out.println(b1 = b2);
3.2 特殊说明的
1.比较运算符的结果都是boolean类型的
2.区分 == 和 =3.<= >= > < :只能使用在数值类型的数据之间
4.==:不仅可以使用在数值类型的数据之间,还可以使用在引用型变量之间 Account acct1 = new Account(1000);
Account acct1 = new Account(2000); boolean b1 == (acct1 == acct2); //比较两个Account是否是同一个账户(或者说是否为同一个对象)
boolean b2 == (acct1 != acct2);
4 逻辑运算符 &(与) && |(或) || ! ^
4.1 典型代码:
boolean b1 = true;
b1 = false;
int num1 = 10;
// & 与 &&
if(b1 & num1 == 10){
System.out.println(\"我要吃冰淇淋!\");
}else{
System.out.pritln(\"我要吃喜之郎!\")
}
if(b1 && num1 == 10){
System.out.println(\"我要吃冰淇淋!\");
}else{
System.out.pritln(\"我要吃喜之郎!\")
}
// | 与 ||
if(b1 | num1 == 10){
System.out.println(\"我要吃冰淇淋!\");
}else{
System.out.pritln(\"我要吃喜之郎!\")
}
if(b1 || num1 == 10){
System.out.println(\"我要吃冰淇淋!\");
}else{
System.out.pritln(\"我要吃喜之郎!\")
}
4.2 特殊说明的
1.逻辑运算符运算的也都是boolean类型的变量,结果也是boolean类型的
2.开发中推荐使用 &&
3.开发中推荐使用 ||
4.3 区分& 与 &&
相同点:
1.& 和 && 运算结果都一样
2.符号左边是true时,两者都会执行符号右边的运算
不同点:1.符号左边是false时,&会执行符号右边的运算,而 && 不会执行符号右边的运算
4.4 区分 | 与 ||
相同点:
1.| 和 || 运算结果都一样
2.符号左边是false时,两者都会执行符号右边的运算
不同点: 1.符号左边是true时,| 会继续执行符号右边的运算,而 || 不会执行符号右边的运算
5 位运算符(了解就行) << >> >>> & | ^ ~(取反)
5.1 典型代码:(面试题) 你能否写出最高效的2 * 8的实现方式?
答案:2 << 3 8 << 1
5.2 特殊说明的
1.位运算符操作的都是整型的数据
2.<< :在一定范围内,每向左移1位,相当于 * 2
3.>> :在一定范围内,每向右移1位,相当于 / 2
6 三元运算符 (条件表达式) ? 表达式1 : 表达式2
6.1 典型代码:
//获取两个整数的较大值
int m = 10;
int n = 5;
int max = (m > n) ? m : n;
System.out.println(max);
//获取三个整数的较大值
int num1 = 10;
int num2 = 29;
int num3 = 3;
//不建议
int max3 = (((num1>num2)? num1:num2)>num3)?((num1>num2)?num1 : num2):num3;
System.out.println(max3);
6.2 特殊说明的
①条件表达式的结果为boolean型
②根据条件表达式的真或假,决定执行表达式1,还是执行表达式2
如果为true,则执行表达式1
如果为false,则执行表达式2
③表达式1和表达式2要求是一致的
④三元运算符是可以嵌套的
⑤凡是可以使用三元运算符的地方都可以改写成if-else结构,使用if-else的不一定能改成三元运算符。反之,不成立。
⑥如果程序既可以使用三元运算符又可以使用if-else,那么优先使用三元运算符
原因:简洁、执行效率高
流程控制
①顺序控制:程序从上到下执行
②分支结构:if-else、switch-case
③循环结构:for循环、while循环、do-while循环
1 补充Scanner的使用
import java.util.Scanner
Scanner sc = new Scanner(System.in);
int i1 = sc.nextInt();
2 分支结构
2.1 if-else条件判断结构
2.1.1 结构:
①
if(条件表达式){
执行表达式
}
②二选一
if(条件表达式){
执行表达式1
}else{
执行表达式2
}
③多选一(n选一)
if(条件表达式){
执行表达式1
}else if{
执行表达式2
}else if{
执行表达式3
}
...
else{
执行表达式n
}
2.1.2说明:
1.if-else是可以嵌套使用的
2.else是可选的
3.if-else结构中的执行语句只有一行时;对应的{}可以省略;But一般不省略
2.2 switch-case选择结构
2.2.1 结构:
结构一:
switch(表达式){
case 1:
执行表达式1;
break;
case 2:
执行表达式2;
break;
case 3:
执行表达式3;
break;
default:
执行表达式4;}
结构二:
switch(表达式){
case 1:
case 2:
执行表达式1;
break;
case 3:
case 4:
执行表达式2;
break;
default:
执行表达式3;}
2.2.2 说明:
①.switch-case结构适用于表达式值较少的时候
②.switch-case根据表达式中的值去依次匹配case,匹配成功会进入case执行语句;执行完之后,如果没有break,那么会继续执行下面的case中的执行语句,知道末尾或者遇到break为止
③.break:可以跳出switch-case结构,break是可选的
④.switch结构中的表达式,只能是以下六种类型:byte、short、char、int、枚举类型、String数据类型
⑤.default相当于if-else中的else,一样是可选的,而且位置是灵活的
⑥如果switch-case中有多个case的执行语句相同,那么可以把case合并在一起
3 循环结构
3.1循环结构的四要素
①初始化条件
②循环条件 (是boolean类型的)
③循环体
④迭代条件说明:通常情况下,循环结束都是因为②中循环条件返回false而结束的
3.2 三种循环结构
3.2.1 for循环结构
for (①;②;④){
③;
}
执行过程:
①-②-③-④-②-③-④·····②
3.2.2 while循环结构
①;
while(②){
③; ④;
}
执行过程:
①-②-③-④-②-③-④·····②
说明:
写While循环一定不要缺少迭代条件,否则会出现死循环
for 和 while 总结:
1.执行过程和if-else一样
2.开发中,基本上都是从for和while两者之间进行选择,实现循环结构
3.for 和 while 是可以相互转换的
区别:for循环和While循环的初始条件的作用域不同
4.写程序时要避免出现死循环
3.2.3 do-while循环结构
①;
do{
③;
④;
}
while(②)
执行过程:
①-③-④-②-③-④-②-③-④-····-②
说明:
1.do-while会先运行一遍循环体和迭代条件
2.当循环执行多次时,do-while和while的结果一样;
当循环只执行一次时,do-while和while的结果不一样;do-while会多运行一次循环体
3.开发中使用for和While更多一些。
3.3 “无限循环”结构: while(true){}或for( ; ; )
总结:如何结束一个循环结构?
1.遇到break、continue,或循环条件为false
3.4 嵌套循环
3.4.1 嵌套循环:
当一个循环结构A声明在另一个循环结构B内部,那么就构成了嵌套循环
(1)内层循环:循环结构A
(2)外层循环:循环结构B
3.4.2 说明:
①.内层循环遍历一遍,外层循环执行一次
②.如果外层循环需要执行m次,内层循环要执行n次,那么内层循环一共执行了多少次?(m * n)
③外层循环控制行数、内层循环控制列数。
3.4.3 典型练习:打印九九乘法表
for(int i=1;i<=9;i++){
for(int j=1;j<=i;j++){
System.out.print(j + \"*\" + i + \"=\" +(i*j) + \"\\t\");
}
System.out.println();
}
4 补充:衡量一个功能代码的优劣:
①正确性
② 可读性
③健壮性
④高效率和低存储:时间复杂度、空间复杂度(衡量算法的好坏)
5 break 和 continue
5.1区分break和continue:
1.相同点:break和continue都是结束循环的语句,都是采取就近原则
2.不同点:break是跳出当层循环;continue是跳出当次循环
5.2 带标签的break和continue:
lable:for(int i=1;i <= 10;i++){
for(int j=1;j < 5;j++){
break;//结束j循环
break lable;//结束i循环
continue;//结束j循环的当次循环
continue lable;//结束i循环的当次循环
}
}
数组的概述
1 数组的理解:
数组(Array),是多个相同数据类型按一定顺序排列的集合,并使用一个名字命名,并通过标号的方式对这些数据进行统一管理。
2 数组相关的概念
①数组名 ②元素 ③下标(索引) ④数组长度:元素的个数
3 数组的特点
①数组是有序排列的
②数组是是属于引用型数据类型的变量。数组的元素可以是基本数据类型的变量,也可以是引用数据类型的变量
③创建的数组对象会在内存中开辟一整块连续的空间
④数组长度一旦确定,就不能更改
4 数组的分类
①按照维数:一维数组、二维数组....
②按照类型:基本数据类型元素的数组、引用数据类型与元素的数组
一维数组
1 一维数组的声明与初始化
1.1 正确的方式:
//声明和初始化分开
int[] arr;
arr = new int[]{1,2,3,4};
//静态初始化:数组的初始化和数组元素的赋值同时操作
int[] arr = new int[]{1,2,3};
int[] arr1 = {1,2,3,4};//类型推断
int arr2[] = new int[]{1,2,3};//[]可以写在int后面,也可以写在数组名后面
int arr3[] = {1,2,3};//new int[]可以去掉
//动态初始化:数组的初始化和数组元素的赋值分开操作
int[] arr4 = new int[4];
1.2 错误的方式:
int[] arr = new int[5]{1,2,3,4,5};//动态和静态结合在一起
int[] arr1 = new int[];
int[5] arr2 = new int[5];
2 一维数组元素的引用:通过使用下标的方式来调用数组某个位置的数据
//角标是从0开始的,到length-1结束
int[] arr = new int[]{9,4,5,7};
System.out.print(arr[1]);
String[] names = new String[3];
names[0] = \"李飞\";
names[1] = \"张三\";
names[2] = \"李四\";
3 数组的属性:length
int[] arr = new int[]{9,4,5,7};
System.out.print(arr.length);//4String[] names = new String[3];
System.out.print(names.length);//3说明:
数组一旦初始化,长度就确定了length
数组一但确定,就不能更改了
4 一维数组的遍历
int[] arr = new int[]{9,4,5,7};
for(int i = 0;i<arr.length;i++){
System.out.print(arr[i] + \" \");
}
5 一维数组元素的默认初始化值
①整型:0
②浮点型:0.0
③char:0 或 ‘/u0000’(打印出来是空白)
④boolean型:false
⑤引用类型:null
6 一维数组的内存结构
int[] arr1 = new int[4];
arr1[0] = 10;
arr1[2] = 20;
String[] arr2 = new String[3];
arr2[1] = “憨憨”;
arr2 = new String[5];
二维数组
1 如何理解二维数组?
数组属于引用数据类型
数组的元素也可以是引用数据类型
若一个一维数组A的元素是由一维数组组成的,那么一维数组A称为二维数组由数组组成的数组
外层数组的元素是由另一个数组组成的
2 二维数组的声明与初始化
2.1 正确的方式:
//声明
int[][] arr;
//初始化
arr = new int[][]{{3,4,6,7},{5,8},{1,9,10}};
//静态初始化
int[][] arr1 = new int[][]{{9,4,5,7},{5,6},{6,1,6}};
//动态初始化
int[][] arr2 = new int[4][];
int[][] arr3 = new int[4][5];
//其他正确写法
int[] arr3[] = new int[4][5];
int arr4[][] = new int[4][5];
int[][] arr5 = new int[4][];
2.2 错误的方式:
int[][] arr = new int[][];
int[][] arr1 = new int[][4];
[][]int arr2 = new int[][];
int[][] arr3 = new int[4][3]{{1,1,1},{2,2,2},{3,3,3}};
int[][] arr4 = int[4][3];//没有写new
3 如何调用二维数组元素
int[][] arr = new int[][]{{9,4,5,7},{5,6},{7,1,8}};
System.out.print(arr[1][1]);//输出6
String[][] arr1 = new String[4][];
String[][] arr2 = new String[4][3];
System.out.print(arr1[1]);//输出null //注意区别
System.out.print(arr2[1]);//输出地址值
4 二维数组的属性:length
int[][] arr = new int[][]{{9,4,5,7},{5,6},{7,1,8,6,3}};
System.out.print(arr1.length);//3
System.out.print(arr1[0].length);//4
System.out.print(arr1[1].length);//2
System.out.print(arr1[2].length);//5
5 二维数组的遍历
int[][] arr = new int[][]{{9,4,5,7},{5,6},{6,1,6}};
for(int i = 0;i < arr.length;i++){
for(int j = 0;j < arr[i].length;j++){
System.out.print(arr[i][j] + \" \");
}
System.out.println();
}
6 二维数组元素的默认初始化值
规定:二维数组分为外层数组的元素和内层数组的元素
int[][] arr = new int[4][3];
外层元素:arr[0]、arr[1]、arr[2]、arr[3];
内层元素:arr[0][0]、arr[0][1]、arr[0][2].......;
数组元素的默认初始化值:
针对初始化方式一:比如:int[][] arr = new int[4][3];
外层元素的初始化值:地址值
内层元素的初始化值:与一维数组的初始化值相同
针对初始化方式二:比如:int[][] arr = new int[4][];
外层元素的初始化值:null
内层元素的初始化值:不能调用
7 二维数组的内存结构
int[][] arr1 = new int[4][];
arr1[1] = new int[]{1,2,3};
arr1[2] = new int[4];
arr1[2][1] = 30
数组的常见算法
1 数组的创建与元素赋值:
杨辉三角(二维数组)、回形数(二维数组)、6个数(范围:[1-30],不能重复)
2 针对于数值型的数组:求最大值、最小值、总和、平均数等。
//求最大值
int max = arr[0];
for(int i = 1;i<arr.length;i++) {
if(max <= arr[i]) {
max = arr[i];
}
}
System.out.println(\"最大值为:\" + max);
//求最大值
int min = arr[0];
for(int i = 1;i<arr.length;i++) {
if(min >= arr[i]) {
min = arr[i];
}
}
System.out.println(\"最小值为:\" + min);
//总和
int sum = 0;
for(int i =-0;i<arr.length;i++) {
sum+=arr[i];
}
System.out.println(\"总和为:\" + sum);
//平均数
int avg = 0;
avg = sum / arr.length;
System.out.println(\"平均数为:\" + avg);
3 数组的赋值与复制
int[] array1,array2;
array1 = new int[]{1,2,3,4};
①赋值
array2 = array1;
如何理解:将array1保存的数组的地址值赋给array2,使得两个数组指向堆空间中的同一个数组实体。
②复制
array2 = new int[array1.length];
for(int i = 0;i<array1.length;i++) {
array2[i] = array1[i];
}
如何理解:我们通过new的方式,给array2在堆空间中新开辟了数组空间,将array1中的元素值一一赋值到array2的数组中。
4 数组元素的反转
int[] arr = new int[]{34,5,22,-98,6,-76,0,-3};
//方法一:
for(int i = 0;i < arr.length/2;i++) {
int temp = arr[i];
arr[i] = arr[arr.length-i-1];
arr[arr.length-i-1] = temp;
}
//方法二:
for(int i = 0,j = ar.length - 1;i < j;i++,j--) {
int temp = arr[i];
arr[i] = arrj];
arr[j] = temp;
}
5 数组中指定元素的查找:检索、搜索
5.1线性查找:
实现思路:通过遍历的方式,一个一个的数据进行比较、查找
适用性:具有普遍适用性即数据类型没有要求、也不要求数据有序
5.2二分法查找:
实现思路:每次取中间值,折半的方式进行搜索
适用性:数据需要有序
6 数组的排序算法:十大内部算法
- 选择排序:直接选择排序、堆排序(知道怎么实现)
- 交换排序:冒泡排序、快速排序(要掌握)
- 插入排序:直接插入排序、折半插入排序、Shell排序
- 归并排序(知道怎么实现)
- 桶式排序
- 基数排序
6.1 衡量排序算法的优劣:
时间复杂度、空间复杂度、稳定性
6.2 排序的分类:内部排序 与 外部排序
1.内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
2.外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排 序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最 常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
6.3 不同排序算法的时间复杂度
冒泡排序的时间复杂度:O(n^2)
快速排序的时间复杂度:O(nlogn)
7 冒泡排序的实现:--需要能手写出来
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = new int[]{34,5,22,-98,6,-76,0,-3};
//排序
for(int i = 0;i < arr.length - 1;i++) {
for(int j = 0;j < arr.length - i -1 ;j++) {
if(arr[j] >= arr[j+1]) {
int temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//遍历输出数组
for(int i = 0;i<arr.length;i++) {
System.out.print(arr[i] + \" \");
}
System.out.println();
}
}
Arrays工具类的使用
1 理解:
①定义在java.util包下
②Arrays:提供了很多操作数组的方法
2 使用:
//1.boolean.equals(int[] a;int[] b);
//2.输出数组
Arrays.toString(arr);
//3.void fill(int[] a,val):将指定值填充到数组中
//4.void sort(int[] a)
//5.int binarySearch(int[] a,int key)//二分法查找
数组的常见异常:
一但程序出现异常,并且未处理,那么将会终止执行
1 数组角标越界异常:ArrayIndexOutOfBoundsException
System.out.println(arr[-2]);
2 空指针异常:NullPointerException
//情况一
int[] arr1 = new int[]{1,2,3}
arr1 = null;
System.out.println(arr1[0]);
//情况二
int[][] arr2 = new int[4][];
System.out.println(arr2[0][0]);
类与对象
1 面向对象的三条主线:
1)类与类的成员:属性、方法、构造器、代码块、内部类
2)面向对象的三大特征:封装、继承、多态
3)其他关键字:this、super、abstract、interface、static、final、package、import
2 面向对象与面向过程:
1)面向过程:强调的功能行为,以函数为最小单位,考虑怎么做。
2)面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
对比举例:把大象放进冰箱。
3 完成一个项目(或功能)的思想:
1)根据问题需要,选择问题所针对的现实世界中的实体。
2)从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
3) 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序 语言,把类构造成计算机能够识别和处理的数据结构。
4)将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
4 面向对象的两个重要概念:类和对象
1)类:对一类实物的描述,抽象的、概念上的内容
对象:是实际存在的该类事物的每个个体,因而也成为实例(instance)
2)面向对象程序设计的重点就是类的设计
设计类、就是设计类的成员。3)类是对象的抽象
对象是有类new出来的,派生出来的
5 面向对象思想落地实现的规则
1)创建类,设计类的成员
2)创建类的对象
3)通过\"对象.属性\"或\"对象.方法\"调用对象的结构
6 补充:几个概念的使用说明
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = method = 函数
创建类的对象 = 类的实例化 = 实例化类
7 对象的创建与对象的内存解析
7.1 典型代码:
Person p1 = new Perosn();
Person p2 = new Perosn();
Person p3 = p1;//没有新建一个对象,共用一个堆空间中的实体对象
7.2 说明:
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性,另一个对象的属性不会改变
7.3 内存解析
8 匿名对象:
我们创建的对象,没有显式的赋给一个变量名。即为匿名对象
8.1 特点:
匿名对象只能调用一次。
8.2 举例:
new Student().属性 或 new Student().方法
new Phone().sendEmail();
new Phone().playGamel();
new Phone().price = 1999;
new Phone().showPrcie = 1999;
8.3 应用场景:
PhoneMall mall = new PhoneMall();
//匿名对象的使用
mall.show(new Phone());
class PhoneMall{
public void show (Phone phone){
phone.sendEmail();
phone.playGame();
}
}
9 理解\"万事万物皆对象\"
1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
Scanner,String等、文件:File、网络资源:URL
2.涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。
10 JVM的内存结构
编译完源程序以后,生成一个或多个字节码文件。
我们使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行。意味着:需要将字节码文件对应的类加载到内存中,涉及到内存解析。
虚拟机栈,即为平时说的栈结构。我们平时把局部变量存储在栈结构中
堆,我们把new出来的结构(数组、对象等)加载到堆空间中。补充:对象的属性(非static的)加载到堆空间中。
方法区:类的加载信息、常量池、静态域
类的结构之一:属性
类的设计中,两个重要结构之一:属性
属性 = 成员变量 = filed = 域、字段
1 对比:属性 vs 局部变量
1.1 相同点:
①定义变量的格式:数据类型 变量名 = 变量值;
②先声明,后使用
③变量都有其对应的作用域
1.2 不同点:
①在类中声明的位置不同:属性直接定义在类的一对{}中;局部变量定义在方法内、方法形参、构造器形参、构造器内部的变量
②关于权限修饰符的不同:属性:可以在声明时,指明其权限,使用权限修饰符。常用的权限修饰符:public 、private、缺省、protected。局部变量不可以使用权限修饰符。
③默认初始值:属性:类的属性根据其类型都有默认初始化值:整型:0、浮点型:0.0、char:0或(\'\\u0000\')、boolean型:false、引用类型(类、接口、数组):null。局部变量:没有初始化值,故在使用前必须赋值,形参在调用时赋值即可。
④在内存中的位置不同:属性:加载在堆空间中;局部变量:加载在栈空间中
1.3 补充:回顾变量的分类
方式一:按照数据类型:
①基础数据类型:byte、short、char、int、long、float、double、boolean
②引用数据类型:类、接口、数组
方式二:按照类中声明的位置:
①成员变量:实例变量(不以static修饰)、类变量(以static修饰)
②局部变量:形参(方法、构造器中定义的变量)、方法局部变量(在方法内定义)、代码块局部变量(在代码块内定义)
类的结构之二:方法
方法:描述类应该具有的功能。
比如:Math类:sqrt()\\random() \\...
Scanner类:nextXxx() ...
Arrays类:sort() \\ binarySearch() \\ toString() \\ equals() \\ ...
1 举例:
public void eat(){}
public void sleep(int hour){}
public String getName(){}
public String getNation(String nation){}
2 方法的声明:
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
注意:static、final、abstract 来修饰的方法,后面再讲。
3 说明:
3.1 关于权限修饰符:
默认方法的权限修饰符先都使用public
Java规定的4种权限修饰符:private、public、缺省、protected -->封装性再细说
3.2 返回值类型:
3.2.1 有返回值 vs 没有返回值
① 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用
return关键字来返回指定类型的变量或常量:“return 数据”
②如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要
使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。
3.2.2我们定义方法该不该有返回值?
① 题目要求
② 凭经验:具体问题具体分析
3.3 方法名:
属于标识符,遵循标识符的规则和规范,“见名知意”
3.4 形参列表:
方法可以声明0个,1个,或多个形参。
1)格式:数据类型1 形参1,数据类型2 形参2,...
2)我们定义方法时,该不该定义形参?
① 题目要求
② 凭经验:具体问题具体分析
3.5 方法体:
方法功能的体现。
类的设计中,两个重要结构之二:方法
方法 = 成员方法 = method = 函数
3.6方法的使用:
在使用中可以调用当前类或方法
特殊地:方法A中可以调用方法A:称为递归调用
注意:方法中不可以调用方法。
4 关键字:return :
① 适用范围:方法体内
② 作用:
1)结束方法体
2)如果方法有返回值,那么可以使用return去返回值
③ 注意点:return后面不能有执行语句
5 方法的重载
5.1 方法的重载的概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参个数或者参类型不同即可。
\"两同一不同\":同一个类、相同方法名
参数列表不同:参数个不同,参类型不同
5.2 构成重载的举例和不构成重载的举例
Arrays类中重载的sort() / binarySearch();Printstream中的println()
//1-4构成了重载
public void getSum(double d1,double d2) {
System.out.println(\"1\");
}
public void getSum(int m,int n) {
System.out.println(\"2\");
}
public void getSum(String str) {
System.out.println(\"3\");
}
public void getSum(String str,String str1) {
System.out.println(\"4\");
}
//1-5不构成了重载
// public double getSum(double d1,double d2) {
// System.out.println(\"5\");
// return d1;
// }
//2-6-7不构成了重载
// public int getSum(int m,int n) {
// System.out.println(\"6\");
// return 0;
// }
// public int getSum(int i,int j) {
// System.out.println(\"7\");
// return 0;
//}
5.3 如何判断是否构成重载
根据定义:方法名是否相同;是否在同一个类中;参数列表是否相同
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
5.4 如何确定类中某一个方法的调用:
在通过对象调用方法时,如何确定调用的是哪一个方法:
方法名 ---> 参数列表
6 可变个数形参的方法
6.1 使用说明:
可变个数形参的方法
1.jdk 5.0新增的内容
2.具体使用:
2.1可变个数形参的格式:数据类型 ... 变量名
2.2当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个。。。。
2.3可变个数形参的方法与本类中方法名相同、形参不同的方法之间构成重载
2.4可变个数形参的方法与本类中方法名相同、形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
2.5可变个数形参在方法的形参中,必须声明在末尾
2.6可变个数形参在方法的形参中,最多只能声明一个可变形参。
6.2 举例说明:
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.method(\"hello\");
test.method(\"hi\",\"china\");
test.method(\"hello\",\"world\",\"!!!\");
}
public void method(String str) {
System.out.println(\"一个参数\");
}
public void method(String str1,String str2) {
System.out.println(\"两个参数\");
}
public void method(String ... str1) {
System.out.println(\"可变参数\");
}
}
7 Java的值传递机制
7.1 针对于方法内变量的赋值举例:
规则:
如果变量是基本数据类型,此时贝武值的是变量所保存的数据值。
如果变量是引用数据类型,此时赃值的是变量所保存的数据的她址值。
7.2 针对于方法的参数概念
形参:方法定义时,声明的小括号内的参数。
实参:方法调用时,实际传递给形参的数据
7.3 java中参数传递机制:值传递机制
规则:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
7.4 典型例题与内存解析:
8 递归方法
8.1.定义:
递归方法:一个方法体内调用自己
8.2 如何理解递归方法?
1.递归方去一个方法体内调用它自身。
2.方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
8.3 举例:
// 计算1-n之间所有的自然数的和
public int getSum(int n) {
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
// 计算1-n之间所有的自然数的乘积
public int getMul(int n) {
if (n == 1) {
return 1;
} else {
return n * getMul(n - 1);
}
}
9 突发奇想
9.1 属性个方法名可不可以相同:(可以)
int age;
public static void main(String[] args) {
MethodArgsTest m = new MethodArgsTest();
m.age = 10;
m.age();
}
public void age() {
System.out.println(age);
}
面向对象的特征一:封装性
1.为什么要引入封装性?
1.我们程序设计追求“高内聚,低耦合”。
①高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
②低耦合 :仅对外暴露少量的方法用于使用。
2.隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提 高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
2.问题引入:
当我们创建一个类的对象以后,我们可以通过\"对象.属性\"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs())同时,我们需要避免用户再使用\"对象.属性\"的方式对属性进行赋值。则需要将属性声明为私有的(private).
-->此时,针对于属性就体现了封装性。
3.封装性思想具体的代码体现:
体现一:将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
private double radius; //私有属性
//set方法
public void setRadius(int radius) {
this.radius = radius;
}
//get方法
public double getRadius() {
return radius;
}
体现二:不对外暴露的私有的方法
体现三:单例模式(将构造器私有化)
体现四:如果不希望类在包外被调用,可以将类设置为缺省的
4.Java规定的四种权限修饰符
4.1 权限从小到大顺序为:
private --> 缺省 --> protected --> public
4.2 具体的修饰范围:
private:当前类
缺省:当前类、当前包
protected:当前类、当前包、不同包下打的子类
public:当前类、当前包、不同包下打的子类、同项目下的包
4.3 权限修饰符可用来修饰的结构说明:
属性、方法、构造器、内部类
修饰类的话能使用两种:public、缺省
类的结构之三:构造器
1 构造器(或构造方法):Constructor
1.1 构造器的作用:
①创建对象 ②初始化对象的信息
2 使用说明:
①如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
②定义构造器的格式:权限修饰符 类名(形参列表){}
③一个类中定义的多个构造器,彼此构成重载
④一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
⑤一个类中,至少会有一个构造器。
3 举例:
//构造器
public Person(){
System.out.println(\"Person().....\");
}
public Person(String n){
name = n;
}
public Person(String n,int a){
name = n;
age = a;
}
4 属性赋值的顺序
① 默认初始化
② 显式初始化
③ 构造器中初始化
上面表示对象初始化的时候,下面表示初始化后续的操作
④ 通过\"对象.方法\" 或 \"对象.属性\"的方式,赋值以上操作的先后顺序:① - ② - ③ - ④
5 JavaBean的概念
JavaBean是一种Java语言写成的可重用组件。
所谓JavaBean,是指符合如下标准的Java类:
①类是公共的
②有一个无参的公共的构造器
③有属性,且有对应的get、set方法
关键字:this
1 可以调用的结构:
属性、方法;构造器
2 this调用属性、方法:
①this理解为:当前对象 或 当前正在创建的对象
②在类的方法中,我们可以使用\"this.属性\"或\"this.方法\"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略\"this.\"。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用\"this.变量\"的方式,表明此变量是属性,而非形参。
③在类的构造器中,我们可以使用\"this.属性\"或\"this.方法\"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略\"this.\"。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用\"this.变量\"的方式,表明此变量是属性,而非形参。
3 this调用构造器:
① 我们在类的构造器中,可以显式的使用\"this(形参列表)\"方式,调用本类中指定的其他构造器
② 构造器中不能通过\"this(形参列表)\"方式调用自己
③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了\"this(形参列表)\"
④ 规定:\"this(形参列表)\"必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个\"this(形参列表)\",用来调用其他的构造器
关键字:package/import
1 package的使用
1.1 使用说明:
1.为了更好的实现项目中类的管理,提供包的概念
2.使用package声明类或接口所属的包,声明在源文件的首行
3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
4.每\".\"一次,就代表一层文件目录。
补充:同一个包下,不能命名同名的接口、类。
不同的包下,可以命名同名的接口、类。
1.2 举例:
①某航运软件系统包括:一组域对象、GUI和reports子系统
②MVC设计模式
1.3 JDK中的主要包介绍:
1)java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和 Thread,提供常用功能
2)java.net----包含执行与网络相关的操作的类和接口。
3)java.io ----包含能提供多种输入/输出功能的类。
4)java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日 期日历相关的函数。
5)java.text----包含了一些java格式化相关的类
6)java.sql----包含了java进行JDBC数据库编程的相关类/接口
7)java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这 些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S
2. import的使用:
import:导入
1)在源文件中显式的使用import结构导入指定包下的类、接口
2)声明在包的声明和类的声明之间
3)如果需要导入多个结构,则并列写出即可
4)可以使用\"xxx.*\"的方式,表示可以导入xxx包下的所有结构
5)如果使用的类或接口是java.lang包下定义的,则可以省略import结构
6)如果使用的类或接口是本包下定义的,则可以省略import结构
7)如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
8)使用\"xxx.*\"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
9)import static:导入指定类或接口中的静态结构:属性或方法。
面向对象的特征二:继承性
1 为什么要有类的继承性?(继承性的好处)
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 继承性是多态性的前提
2 继承的格式
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
3 子类继承父类以后有哪些不同?
① 体现:一旦子类A继承父类8以后,子类A中就获取了父类B中声明的所有的属性和方法。
特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
② 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
子类和父类的关系,不同于子集和集合的关系。
extends:延展、扩展
4 Java中继承性的说明
1.一个类可以被多个子类继承。
2.Java中类的单继承性:一个类只能有一个父类
3.子父类是相对的概念
4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
5 java.lang.Object类的理解
1.如果我们没有显式声明一个类的父类的话,则此类继承于java.lang.Object类
2.所有的java类(除了java.lang.Object类)都直接或间接的继承于java.lang.Object类
3.所有的java类都具有java.lang.Object类的功能
方法的重写
1.什么是方法的重写(override 或 overwrite)?
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
2. 应用:
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
3.举例:
class Circle(){
public double findArea(){}//求面积
}
class Cylinder extends Circle{
public double findArea(){}//求表面积
}
**************************
4.重写的规则:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符特殊情况:子类不能重写父类中声明为private权限的方法
③ 返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
子和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
5.面试题:(区分方法的重写和重载?)
①二者的概念
②二者具体的规则
③重载不表现为多态性
重写表现为多态性
关键字:super
1.super 关键字可以理解为:父类的
2.可以用来调用的结构:属性、方法、构造器
3.super调用属性、方法:
①我们可以在子类的方法或构造器中。通过使用\"super.属性\"或\"super.方法\"的方式,显式的调用
父类中声明的属性或方法。但是,通常情况下,我们习惯省略\"super.\"
②特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
使用\"super.属性\"的方式,表明调用的是父类中声明的属性。
③ 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
使用\"super.方法\"的方式,表明调用的是父类中被重写的方法。
4.super调用构造器:
①我们可以在子类的构造器中显式的使用\"super(形参列表)\"的方式,调用父类中声明的指定的构造器
②\"super(形参列表)\"的使用,必须声明在子类构造器的首行!
③ 我们在类的构造器中,针对于\"this(形参列表)\"或\"super(形参列表)\"只能二选一,不能同时出现
④ 在构造器的首行,没有显式的声明\"this(形参列表)\"或\"super(形参列表)\",则默认调用的是父类中空参的构造器:super()
⑤ 在类的多个构造器中,至少有一个类的构造器中使用了\"super(形参列表)\",调用父类中的构造器
子类对象实例化全过程
1.从结果上看:体现的就是继承性
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
2.从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有
父类中的结构,子类对象才可以考虑进行调用。
3.强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
面向对象的特征三:多态性
1.多态性的理解:
可以理解为一个事物的多种形态。
2.何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
3.多态性的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译,看左边;运行,看右边。
4.多态性的使用前提:
① 类的继承关系 ② 方法的重写
5.多态性的应用举例:
Animal animal = new Dog();
Animal animal1 =new Cat();
举例二:
class Order{
public void method(Object obj){
}
}
6.多态性使用的注意点:
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
7.关于向上转型与向下转型:
7.1 向上转型:多态
7.2 向下转型:
7.2.1 为什么使用向下转型:
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特有的属性和方法?向下转型。
7.2.2 如何实现向下转型:
使用强转符()
7.2.3 使用时的注意点:
①使用强转时,可能出现ClassCastException的异常。
②为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
7.2.4 instanceof的使用:
①a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
②如果 a instanceof A返回true,则 a instanceof B也返回true。其中,类B是类A的父类。
③要求a所属的类与类A必须是子类和父类的关系,否则编译错误。
7.2.5 图示:
8.面试题:
8.1 谈谈你对多态性的理解?
①实现代码的通用性
②Object类中定义的public boolean equals(Object obj){}
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
③抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
8.2 多态是编译时行为还是运行时行为?
运行时行为
Object类的使用
1.java.lang.Object类的说明:
1.Object类是所有Java类的根父类
2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
3.Object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()
wait()、notify()、notifyAll()
4.Object类只声明了一个空参的构造器
2.equals()方法
2.1 equals()的使用:
1.是一个方法,而非运算符
2.只能适用于引用数据类型
3.Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
4.像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的\"实体内容\"是否相同。
5.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的\"实体内容\"是否相同。那么,我们就需要对Object类中的equals()进行重写。重写的原则:比较两个对象的实体内容是否相同.
2.2 如何重写equals()
2.2.1 手动重写举例:
public class User {
String name;
int age;
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof User) {
User user = (User) obj;
if(this.name.equals(user.name) && this.age == user.age) {
return true;
}
}
return false;
}
}
2.2.2 开发中如何实现:自动生成
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
return age == other.age && Objects.equals(name, other.name);
}
2.3 回顾 == 运算符的使用:
== :运算符
1.可以使用在基本数据类型变量和引用数据类型变量中
2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
3.toString()方法
3.1 toString()的使用:
1.当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
2.Object类中toString()的定义:
public String toString() {
return getClass().getName() + \"@\" + Integer.toHexString(hashCode());
}
3.像String、Date、File、包装类等都重写了Object类中的toString()方法。
使得在调用对象的toString()时,返回\"实体内容\"信息
4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的\"实体内容\"
3.2 如何重写toString()
@Override
public String toString() {
return \"Person [name=\" + name + \", age=\" + age + \"]\";
}
4.面试题:
①final、finally、finalize的区别?
②== 与 equals的区别
单元测试方法
步骤:
1.选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
2.创建Java类,进行单元测试。
此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
3.此类中声明单元测试方法。
此时的单元测试方法:方法的权限是public,没有返回值,没有形参
4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
说明:
1.如果执行结果没有任何异常:绿条
2.如果执行结果出现异常:红条
包装类的使用
1.为什么要有包装类(或封装类)
为了使基本数据类型的变量具有类的特征,引入了包装类
2.基本数据类型与对应的包装类:
byte ----> Byte
short ----> Short
int ----> Integer
long ----> Long
float ----> Float
doubel ----> Double
boolean ----> Boolean
char ----> Character
前六个的父类是Number
3.需要掌握的类型间的转换:
3.1 简易版:
基本数据类型<--->包装类:自动装箱、自动拆箱
基本数据类型、包装类<--->String:调用String的valueOf(Xxx xxx)
String<--->基本数据类型、包装类:调用包装类的parseXxx(String s)
注意:转换时,可能会报NumberFormatException
3.2 应用场景举例:
①Vector类中关于添加元素,只定义了形参的Object类型的方法:
v.addElement(Object obj)//基本数据类型 -- > 包装类 --> 使用多态
关键字:static
1.可以用来修饰的结构:
主要修饰类的内部结构:属性、方法、代码块、内部类
2.static修饰属性:静态变量(或类变量)
1)属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。 当修改其中一个对象中的
非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对 象修改静态变量时,会导致
其他对象调用此静态变量时,是修改过了的。
2) static修饰属性的其他说明:
① 静态变量随着类的加载而加载。可以通过\"类.静态变量\"的方式进行调用
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
④ 类变量 实例变量
类 yes no
对象 yes yes3) 静态属性举例:System.out; Math.PI;
3.静态变量内存解析:
4.static修饰方法:静态方法
① 随着类的加载而加载,可以通过\"类.静态方法\"的方式进行调用
② 静态方法 非静态方法
类 yes no
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
5.static的注意点:
1)在静态的方法内,不能使用this关键字、super关键字
2)关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
6.如何判定属性和方法应该使用static关键字:
6.1 关于属性
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常常声明为static
6.2 关于方法
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
7.使用举例:
举例一:Math、Arrays、Collections等工具类
举例二:单例模式
举例三:
class Circle{
private double radius;
private int id;//自动赋值
public Circle(){
id = init++;
total++;
}
public Circle(double radius){
this();
// id = init++;
// total++;
this.radius = radius;
}
private static int total;//记录创建的圆的个数
private static int init = 1001;//static声明的属性被所有对象所共享
public double findArea(){
return 3.14 * radius * radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
}
单例模式
1.设计模式的说明
1.1 理解
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。
1.2 常用设计模式:23种经典设计模式
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共,适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解釋器模式。
2.单例模式
2.1 要解决的问题:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
2.2 具体代码的实现:
2.2.1 饿汉式
//饿汉式1
class Bank{
//1.内部私有的构造器(私有化类的)
private Bank() {
}
//2.创建一个私有的对象(内部创建类的对象)
//4.对象也需要是静态的(要求此对象也必须声明是静态的)
private static Bank instance = new Bank();
//3.提供一个公共的静态方法供外部使用(提供公共的方法,返回类的对象)
public static Bank getInstance() {
return instance;
}
}
//饿汉式2
class Order{
//1.私有化构造器
private Order() {
}
//2.声明一个私有的对象,但是没有初始化
//4.此对象也必须是静态的
private static Order instance = null;
static{
instance = new Order();
}
//3.提供一个公共的静态方法,供外部调用
public static Order getInstance() {
return instance;
}
}
2.2.2 懒汉式
//懒汉式
class Order{
//1.私有化构造器
private Order() {
}
//2.声明一个私有的对象,但是没有初始化
//4.此对象也必须是静态的
private static Order instance = null;
//3.提供一个公共的静态方法,供外部调用
public static Order getInstance() {
if(instance == null) {
instance = new Order();
}
return instance;
}
}
2.3 两种方式的对比:
饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的。
懒汉式:
好处:延迟对象的创建
坏处:目前的写法线程是不安全的--->多线程的时候再修改
main()的使用说明
1.说明
main()方法作为程序的入口
main()方法也是一个普通的静态方法
main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
2.如何将控制台获取的数据传给形参:String[] args
运行时:java 类名 \"Tom\" \"Jerry\" \"hanhan\" \"616\" \"true\"
sysou(args[0]);Tom
sysou(args[3]);616
sysou(args[4]);true
sysou(args[5]);报异常
3.分开理解
①public ②static ③void④ main(⑤String[] args){⑥//方法体}
①权限修饰符:private、缺省、protected、public --> 封装性
②修饰符:static、final、abstract、native可以用来修饰方法
③返回值类型:无返回值 void、有返回值的(一定要用return)
④方法名:需要满足标识符的命名规则、规范,且要见名知意
⑤形参列表:重载 vs 重写;值传递机制;体现对象的多态性
⑥方法体:体现方法的功能
类的结构之四:代码块
1.代码块的作用:
用来初始化类、对象的信息
2.分类:
静态代码块、非静态代码块
3.区别
3.1 静态代码块:
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
3.2 非静态代码块:
内部可以有输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
4.Java程序初始化顺序
由父及子,静态先行
1)父类的静态变量
2)父类的静态代码块
3)子类的静态变量
4)子类的静态代码块
5)父类的非静态变量
6)父类的非静态代码块
7)父类的构造方法
8)子类的非静态变量
9)子类的非静态代码块
10)子类的构造方法
属性的赋值顺序
对属性可以赋值的位置:
①默认初始化
②显式初始化/⑤在代码块中赋值
③构造器中初始化
④有了对象以后,可以通过\"对象.属性\"或\"对象.方法\"的方式,进行赋值
执行的先后顺序:① - ② / ⑤ - ③ - ④
关键字:final
final:最终的
1.可以用来修饰:
类、方法、变量
2.具体的:
1)final 用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类2)final 用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass();3)final 用来修饰变量:此时的\"变量\"就称为是一个常量
①final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
②final修饰局部变量:
尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值
以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局常量
关键字:abstract
1.可以用来修饰:类、方法
2.抽象类:
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作--->抽象的使用前提:继承性
3.抽象方法:
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
4.注意点:
1.abstract不能用来修饰:属性、构造器等结构
2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类
5.abstract的应用举例:
//举例一
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 }
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法}
}
//举例二
abstract class GeometricObject{
public abstract double findArea();
}
class Circle extends GeometricObject{
private double radius;
public double findArea(){
return 3.14 * radius * radius;
}
}
模板方法的设计模式
1.解决的问题
在软件开发中实现一个算法时,整体步骤很固定、通用, 这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽 象出来,供不同子类实现。这就是一种模板模式。
2.举例
abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){
long start = System.currentTimeMillis();
this.code();//不确定的部分、易变的部分
long end = System.currentTimeMillis();
System.out.println(\"花费的时间为:\" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
for(int i = 2;i <= 1000;i++){
boolean isFlag = true;
for(int j = 2;j <= Math.sqrt(i);j++){
if(i % j == 0){
isFlag = false;
break;
}
}
if(isFlag){
System.out.println(i);
}
}
}
}
3.应用场景
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的 影子,比如常见的有:
1)数据库访问的封装
2)Junit单元测试
3)JavaWeb的Servlet中关于doGet/doPost方法调用
4)Hibernate中模板程序
5)Spring中JDBCTemlate、HibernateTemplate等
关键字:interface
1.使用说明:
1)接口使用interface来定义
2)Java中,接口和类是并列的两个结构
3)如何定义接口:定义接口中的成员
① JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final的.但是书写时,可以省略不写
抽象方法:public abstract的
② JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)4)接口中不能定义构造器的!意味着接口不可以实例化
5)Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类6)Java类可以实现多个接口 --->弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE7)接口与接口之间可以继承,而且可以多继承
8)接口的具体使用,体现多态性
9)接口,实际上可以看做是一种规范
2.举例:
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println(\"具体传输数据的细节\");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println(\"U盘开启工作\");
}
@Override
public void stop() {
System.out.println(\"U盘结束工作\");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println(\"打印机开启工作\");
}
@Override
public void stop() {
System.out.println(\"打印机结束工作\");
}
}
1.接口使用上也满足多态性
2.接口,实际上就是定义了一种规范
3.开发中,体会面向接口编程!
3.体会面向接口编程的思想
面向接口编程:我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个
数据库厂商的API。
4.Java8中关于接口的新规范
//知识点1:接口中定义的静态方法,只能通过接口来调用。 //知识点2:通过实现类的对象,可以调用接口中的默认方法。 //如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法 //知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法, //那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则 //知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法, //那么在实现类没有重写此方法的情况下,报错。-->接口冲突。 //知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法 public void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); }
5.面试题:
5.1 抽象类与接口的异同?
相同点:不能 实例化,都可以包含抽象方法的
不同点:1)把抽象类和接口(java7,java8,java9)的定义、内部结构解释说明
2)类:单继承性;接口:多继承性
类与接口之间的关系:多实现
代理模式
1.解决的问题
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其 他对象提供一种代理以控制对这个对象的访问。
2.举例
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println(\"真实的服务器访问网络\");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println(\"联网之前的检查工作\");
}
@Override
public void browse() {
check();
work.browse();
}
}
3.应用场景
应用场景:
1)安全代理:屏蔽对真实角色的直接访问。
2) 远程代理:通过代理类处理远程方法调用(RMI)
3)延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理 模式,当需要查看图片时,用proxy来进行大图片的打开。分类
1)静态代理(静态定义代理类)
2)动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
工厂的设计模式
1.解决的问题
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离 起来,达到提高灵活性的目的。
2.具体模式
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品, 需要修改已有代码)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无 能为力;支持增加产品族)
核心本质: 实例化对象,用工厂方法代替 new 操作。 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
类的结构之五:内部类
1.定义:
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
2.内部类的分类:
成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
3.成员内部类的理解:
一方面,作为外部类的成员:
调用外部类的结构
可以被static修饰
可以被4种不同的权限修饰
另一方面,作为一个类:
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以被abstract修饰
4.成员内部类:
4.1如何创建成员内部类的对象?(静态的,非静态的)
Java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类(见如下代码)
//创建静态的Dog内部类的实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
//创建非静态的Bird内部类的实例(非静态的成员内部类):
// Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
4.2如何在成员内部类中调用外部类的结构?
class Person{
String name = \"小明\";
//非静态成员内部类
class Bird{
String name = \"杜鹃\";
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
}
}
5.局部内部类的使用:
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
//
// return new MyComparable();
//方式二:
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
6. 注意点:
在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,
要求此局部变量声明为final的。
jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明
public class InnerClassTest {
public void method(){
//局部变量
int num = 10;
class AA{
public void show(){
// num = 20;
System.out.println(num);
}
}
}
}
7. 总结:
1)成员内部类和局部内部类,在编译后都会生成字节码文件
格式:成员内部类:外部类\\(内部类名.class
局部内部类:外部类\\)数字 内部类名.class
异常
1.异常的体系结构
java.lang.Throwable
|-----java.lang.Error:一般不编写针对性的代码进行处理。
|-----java.lang.Exception:可以进行异常的处理
|------编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|------运行时异常(unchecked,RuntimeException)
|-----NullPointerException
|-----ArrayIndexOutOfBoundsException
|-----ClassCastException
|-----NumberFormatException
|-----InputMismatchException
|-----ArithmeticException
2.从程序执行过程,看编译时异常和运行时异常
编译时异常:执行javac.exe命令时,可能出现的异常
运行时异常:执行java.exe命令时,出现的异常
3.常见的异常类型,请举例说明:
//******************以下是编译时异常***************************
//FileNotFoundException
@Test
public void test7(){
// File file = new File(\"hello.txt\");
// FileInputStream fis = new FileInputStream(file);
// int data = fis.read();
// while(data != -1){
// System.out.print((char)data);
// data = fis.read();
// }
// fis.close();
}
//******************以下是运行时异常***************************
//ArithmeticException
@Test
public void test6(){
int a = 10;
int b = 0;
System.out.println(a / b);
}
//InputMismatchException
@Test
public void test5(){
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();
System.out.println(score);
scanner.close();
}
//NumberFormatException
@Test
public void test4(){
String str = \"123\";
str = \"abc\";
int num = Integer.parseInt(str);
}
//ClassCastException
@Test
public void test3(){
Object obj = new Date();
String str = (String)obj;
}
//IndexOutOfBoundsException
@Test
public void test2(){
//ArrayIndexOutOfBoundsException
// int[] arr = new int[10];
// System.out.println(arr[10]);
//StringIndexOutOfBoundsException
String str = \"abc\";
System.out.println(str.charAt(3));
}
//NullPointerException
@Test
public void test1(){
// int[] arr = null;
// System.out.println(arr[3]);
String str = \"abc\";
str = null;
System.out.println(str.charAt(0));
}
异常的处理
1. java异常处理的抓抛模型
过程一:\"抛\":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。关于异常对象的产生:① 系统自动生成的异常对象② 手动的生成一个异常对象,并抛出(throw)
过程二:\"抓\":可以理解为异常的处理方式:① try-catch-finally ② throws
2. 异常处理方式一:try-catch-finally
2.1 使用说明:
- finally是可选的。
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码
- catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
- 常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()
- 在try结构中声明的变量,再出了try结构以后,就不能再被调用
- try-catch-finally结构可以嵌套
总结:如何看待代码中的编译时异常和运行时异常?
体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
针对于编译时异常,我们说一定要考虑异常的处理。
2.2:finally的再说明:
1.finally是可选的
2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
3. 异常处理方式二:throws
\"throws + 异常类型\"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
4. 对比两种处理方式
try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉
5. 体会开发中应该如何选择两种处理方式?
1)如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
2) 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
6. 方法重写异常抛出的规则:
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
手动抛出异常对象
1.使用说明
在程序行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象
2.throw 和 throws 的区别
throw:表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws:属于异常处理的一种方式,声明在方法的声明处:
3.典型例题
class Student{
private int id;
public void regist(int id) throws Exception {
if(id > 0){
this.id = id;
}else{
//手动抛出异常对象
// throw new RuntimeException(\"您输入的数据非法!\");
// throw new Exception(\"您输入的数据非法!\");
throw new MyException(\"不能输入负数\");
}
}
@Override
public String toString() {
return \"Student [id=\" + id + \"]\";
}
}
自定义异常类
1.如何自定义一个异常类?
1. 继承于现有的异常结构:RuntimeException 、Exception
2. 提供全局常量:serialVersionUID
3. 提供重载的构造器
public class MyException extends Exception{
static final long serialVersionUID = -7034897193246939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
程序、进程、线程的理解
1. 程序(programm)
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
2. 进程(process)
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期
说明
①如:运行中的QQ,运行中的MP3播放器
②程序是静态的,进程是动态的
③进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3. 线程(thread)
线程(thread) 进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:
①若一个进程同一时间并行执行多个线程,就是支持多线程的
②线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小
③一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。
4. 进程和线程的栈和堆
①进程可以细化为多个线程。
②每个进程,拥有自己独立的:虚拟机栈、程序计数器
③多个线程,共享同一个进程中的结构:方法区、栈
并行与并发
1. 单核CPU与多核CPU的理解
①单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。
② 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
③一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
2. 并行与并发的理解
①并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
②并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
创建多线程的两种方式
1. 方式一:继承Thread类的方式:
多线程的创建,方式一:继承于Thread类
1. 创建一个继承于Thread类的子类
2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
3. 创建Thread类的子类的对象
4. 通过此对象调用start():①启动当前线程②调用当前线程的run()
public class ExtendsThread {
public static void main(String[] args) {
Extends e1 = new Extends();
e1.start();
}
}
class Extends extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
说明两个问题:
①我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
②如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()
2. 方式二:实现Runnable接口的方式:
创建多线程的方式二:实现Runnable接口
1. 创建一个实现了Runnable接口的类
2. 实现类去实现Runnable中的抽象方法:run()
3. 创建实现类的对象
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用start()
public class ImplementsRunaable {
public static void main(String[] args) {
Implements i1 = new Implements();
Thread t1 = new Thread(i1);
t1.start();
}
}
class Implements implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
3. 两种方式的对比:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。实现方式的好处:
①避免了单继承的局限性
② 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。开发中:优先选择:实现Runnable接口的方式
原因:1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合来处理多个线程有共享数据的情况。联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
Thread类中的常用方法
Thread类中的常用的方法:
1)start():启动当前线程;调用当前线程的run()
2)run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3)currentThread():静态方法,返回执行当前代码的线程
4)getName():获取当前线程的名字
5)setName():设置当前线程的名字
6)yield():释放当前cpu的执行权
7)join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
8)stop():已过时。当执行此方法时,强制结束当前线程。
9)sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
10)isAlive():判断当前线程是否存活
线程的优先级
1)MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级2)如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级3)说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
线程通信
wait() / notify() / notifyAll() :此三个方法定义在Object类中
补充:线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
①它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
②守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
③ Java垃圾回收就是一个典型的守护线程。
④ 若JVM中都是守护线程,当前JVM将退出。
⑤ 形象理解:兔死狗烹,鸟尽弓藏
Thread的生命周期
图示:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
说明:
1)生命周期关注两个概念:状态、相应的方法
2)关注:状态a --> 状态b :哪些方法执行了(回调方法)
某个方法主动调用:状态a --> 状态b
3)阻塞:临时状态,补课作为最终状态
死亡:最终状态
线程的同步机制
1. 背景
例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
2. Java解决方案:同步机制
在Java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身方式三:Lock锁 --- JDK5.0新增
1.面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())2.优先使用顺序:
Lock --> 同步代码块(已经进入了方法体,分配了相应资源) --> 同步方法(在方法体之外)
3. 利弊
①同步的方式,解决了线程的安全问题。---好处
②操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性
4.面试题:
4.1 Java是如何解决线程安全问题的,有几种方式?并对比几种方式的不同
3种方式:参上
4.2 synchronized和Lock方式解决线程安全问题的对比
参上:方式三
线程安全的单例模式(懒汉式)
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if(instance == null){
synchronized (Bank.class) {
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
死锁问题
1.死锁的理解:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
2.说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
2)我们使用同步时,要避免出现死锁。
3.举例:
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append(\"a\");
s2.append(\"1\");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append(\"b\");
s2.append(\"2\");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append(\"c\");
s2.append(\"3\");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append(\"d\");
s2.append(\"4\");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
线程通信
1. 线程通信涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
2. 说明:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
3. 面试题:sleep() 和 wait()的异同?
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
4. 小结关于锁的操作:
小结释放锁的操作:
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。
小结不会释放锁的操作:
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
JDK5.0新增线程创建的方式
新增方式一:实现Callable接口 ------ JDK5.0新增
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println(\"总和为:\" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
说明:
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
1.call()可以有返回值的。
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3.Callable是支持泛型的
新增方式二:使用线程池
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + \": \" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + \": \" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
说明:
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止s
面试题:Java中多线程的创建有几种方式?
4种:
1)继承Thread
2)实现Runnable
3)实现Callable
4)线程池
String类
java.lang.String类的使用
1.概述
String:字符串,使用一对\"\"引起来表示。
1.String声明为final的,不可被继承
2.String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3.String内部定义了final char[] value用于存储字符串数据
4.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
5.字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回为true)的字符串的。
2.String的不可变性
2.1 说明
String:代表不可变的字符序列。简称:不可变性。
体现:
1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
2.2 代码举例
@Test
public void test1(){
String s1 = \"abc\";//字面量的定义方式
String s2 = \"abc\";
s1 = \"hello\";
System.out.println(s1 == s2);//比较s1和s2的地址值
System.out.println(s1);//hello
System.out.println(s2);//abc
System.out.println(\"*****************\");
String s3 = \"abc\";
s3 += \"def\";
System.out.println(s3);//abcdef
System.out.println(s2);
System.out.println(\"*****************\");
String s4 = \"abc\";
String s5 = s4.replace(\'a\', \'m\');
System.out.println(s4);//abc
System.out.println(s5);//mbc
}
2.3 图示
3.String实例化的不同方式
3.1 方式说明
方式 一:通过字面量定义的方式
方式二:通过new + 构造器的方式
3.2 代码举例
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = \"javaEE\";
String s2 = \"javaEE\";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String(\"javaEE\");
String s4 = new String(\"javaEE\");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
3.3 面试题
3.3.1 String s = new String(\"abc\");方式创建对象,在内存中创建了几个对象?
两个;一个是堆空间中的new结构,另一个是value[]对应的常量池中的数据集:“abc”
3.4 图示
4.字符串拼接方式赋值的对比
4.1 说明
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中
4.2 代码举例
String s1 = \"javaEE\";
String s2 = \"hadoop\";
String s3 = \"javaEEhadoop\";
String s4 = \"javaEE\" + \"hadoop\";
String s5 = s1 + \"hadoop\";
String s6 = \"javaEE\" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
==================================================
String s1 = \"javaEEhadoop\";
String s2 = \"javaEE\";
String s3 = s2 + \"hadoop\";
System.out.println(s1 == s3);//false
final String s4 = \"javaEE\";//s4:常量
String s5 = s4 + \"hadoop\";
System.out.println(s1 == s5);//true
5.常用方法:
1.int length():返回字符串的长度: return value.length
2.char charAt(int index): 返回某索引处的字符return value[index]
3.boolean isEmpty():判断是否是空字符串:return value.length == 0
4.String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
5.String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
6.String trim():返回字符串的副本,忽略前导空白和尾部空白
7.boolean equals(Object obj):比较字符串的内容是否相同
8.boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
9.String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
10.int compareTo(String anotherString):比较两个字符串的大小
11.String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
12.String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
//1-6
@Test
public void test(){
String s1 = \"HelloWorld\";
System.out.println(s1.length());//返回字符串的长度
System.out.println(s1.charAt(5));//返回某索引处的字符
System.out.println(s1.isEmpty());//判断是否是空字符串
System.out.println(s1.toUpperCase());//使用默认语言环境,将 String 中的所有字符转换为大写
System.out.println(s1.toLowerCase());//使用默认语言环境,将 String 中的所有字符转换为小写
String s2 = \" Han Han \";
String s3 = s2.trim();
System.out.println(\"----\" + s2 + \"----\");
System.out.println(\"----\" + s3 + \"----\");//返回字符串的副本,忽略前导空白和尾部空白
}
//7-12
@Test
public void test1(){
String s1 = \"JavaEE\";
String s2 = \"Javaee\";
System.out.println(s1.equals(s2));//比较字符串的内容是否相同
System.out.println(s1.equalsIgnoreCase(s2));//与equals方法类似,忽略大小写
System.out.println(s1.concat(\"Hadoop\"));//将指定字符串连接到此字符串的结尾。 等价于用“+”
String s3 = \"abc\";
String s4 = \"abe\";
System.out.println(s3.compareTo(s4));//比较两个字符串的大小
String s5 = \"0123456789\";
String s6 = s5.substring(2);//返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
System.out.println(s6);
String s7 = s5.substring(2, 8);
System.out.println(s7);
}
1.boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
2.boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
3.boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
4.boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
5.int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
6.int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
7.int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
8.int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
//1-3
@Test
public void test(){
String s1 = \"HelloWorld\";
System.out.println(s1.endsWith(\"ld\"));//测试此字符串是否以指定的后缀结束
System.out.println(s1.startsWith(\"he\"));//测试此字符串是否以指定的前缀开始
System.out.println(s1.startsWith(\"Wo\",5));//测试此字符串从指定索引开始的子字符串是否以指定前缀开始
}
//4-8
@Test
public void test1(){
String s1 = \"helloWorld\";
boolean contains = s1.contains(\"or\");//当且仅当此字符串包含指定的 char 值序列时,返回 true
System.out.println(contains);
System.out.println(\"********************************\");
String s2 = \"www.hanhanz.top\";
int index = s2.indexOf(\"han\");//返回指定子字符串在此字符串中第一次出现处的索引
System.out.println(index);
int index1 = s2.indexOf(\"ha\", 6);//返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
System.out.println(index1);
System.out.println(\"********************************\");
String s3 = \"han han top han han\";
int index2 = s2.lastIndexOf(\"an\");//返回指定子字符串在此字符串中最右边出现处的索引(即出现的最后位置)
System.out.println(index2);
int index3 = s3.lastIndexOf(\"han\", s3.length()/2);//返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
// fromIndex可以使用lenth
System.out.println(index3);//(即从指定索引位置往前搜索)
}
1.String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
2.String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
3.String replaceAll(String regex, String replacement):使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
4.String replaceFirst(String regex, String replacement):使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
5.boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
6.String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
7.String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
@Test
public void test(){
String s1 = \"北上广深北京上海\";
String s2 = s1.replace(\'北\', \'东\');//返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
System.out.println(s2);
String s3 = s1.replace(\"北京\", \"曹县\");//使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
System.out.println(s3);
String str = \"12hello34world5java7891mysql456\";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
//replaceAll:使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。s
String string = str.replaceAll(\"\\\\d+\",\",\").replaceAll(\"^,|,$\",\"\");
System.out.println(string);
//使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
String string1 = str.replaceFirst(\"\\\\d+\", \",\");
System.out.println(string1);
}
@Test
public void test1(){
String str = \"12345\";
//判断str字符串中是否全部有数字组成,即有1-n个数字组成
boolean matches = str.matches(\"\\\\d+\");//告知此字符串是否匹配给定的正则表达式。
System.out.println(matches);
String tel = \"0571-4534289\";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches(\"0571-\\\\d{7,8}\");
System.out.println(result);
System.out.println();
//根据给定正则表达式的匹配拆分此字符串。
str = \"hello|world|java\";
String[] strs = str.split(\"\\\\|\");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println();
//根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
String str2 = \"hello.world.java\";
String[] strs2 = str2.split(\"\\\\.\",2);
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
}
6.String与其它结构的转换
6.1 与基本数据类型、包装类之间的转换
String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
@Test
public void test1(){
String str1 = \"123\";
// int num = (int)str1;//错误的
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);//\"123\"
String str3 = num + \"\";
System.out.println(str1 == str3);
}
6.2 与字符数组之间的转换
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器
@Test
public void test2(){
String str1 = \"abc123\"; //题目: a21cb3
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{\'h\',\'e\',\'l\',\'l\',\'o\'};
String str2 = new String(arr);
System.out.println(str2);
}
6.3 与字节数组之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
编码:字符串 -->字节 (看得懂 --->看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = \"abc123中国\";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes(\"gbk\");//使用gbk字符集进行编码。
System.out.println(Arrays.toString(gbks));
System.out.println(\"******************\");
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println(str2);
String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, \"gbk\");
System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}
6.4 与StringBuffer、StringBuilder之间的转换
String ---> StringBuffer、StringBuilder:调用StringBuffer、StringBuilder的构造器
StringBuffer、StringBuilder ---> String:①调用String的构造器;②StringBuffer、StringBuilder的toString()
7.JVM中字符串常量池存放位置说明:
jdk1.6(JDK6.0):字符串常量池存储在方法区(永久区)
jdk1.7(JDK7.0):字符串常量池存储在堆空间
jdk1.8(JDK8.0):字符串常量池存储在方法区(元空间)
8.常见算法题目的考查:
8.1 模拟一个trim方法,去除字符串两端的空格。
public String myTrim(String str) {
if (str != null) {
int start = 0;// 用于记录从前往后首次索引位置不是空格的位置的索引
int end = str.length() - 1;// 用于记录从后往前首次索引位置不是空格的位置的索引
while (start < end && str.charAt(start) == \' \') {
start++;
}
while (start < end && str.charAt(end) == \' \') {
end--;
}
if (str.charAt(start) == \' \') {
return \"\";
}
return str.substring(start, end + 1);
}
return null;
}
8.2将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反 转为”abfedcg”
// 方式一:
public String reverse1(String str, int start, int end) {// start:2,end:5
if (str != null) {
// 1.
char[] charArray = str.toCharArray();
// 2.
for (int i = start, j = end; i < j; i++, j--) {
char temp = charArray[i];
charArray[i] = charArray[j];
charArray[j] = temp;
}
// 3.
return new String(charArray);
}
return null;
}
// 方式二:
public String reverse2(String str, int start, int end) {
// 1.
String newStr = str.substring(0, start);// ab
// 2.
for (int i = end; i >= start; i--) {
newStr += str.charAt(i);
} // abfedc
// 3.
newStr += str.substring(end + 1);
return newStr;
}
// 方式三:推荐 (相较于方式二做的改进)
public String reverse3(String str, int start, int end) {// ArrayList list = new ArrayList(80);
// 1.
StringBuffer s = new StringBuffer(str.length());
// 2.
s.append(str.substring(0, start));// ab
// 3.
for (int i = end; i >= start; i--) {
s.append(str.charAt(i));
}
// 4.
s.append(str.substring(end + 1));
// 5.
return s.toString();
}
@Test
public void testReverse() {
String str = \"abcdefg\";
String str1 = reverse3(str, 2, 5);
System.out.println(str1);// abfedcg
}
8.3获取一个字符串在另一个字符串中出现的次数。 比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数
// 判断str2在str1中出现的次数
public int getCount(String mainStr, String subStr) {
if (mainStr.length() >= subStr.length()) {
int count = 0;
int index = 0;
// while((index = mainStr.indexOf(subStr)) != -1){
// count++;
// mainStr = mainStr.substring(index + subStr.length());
// }
// 改进:
while ((index = mainStr.indexOf(subStr, index)) != -1) {
index += subStr.length();
count++;
}
return count;
} else {
return 0;
}
}
@Test
public void testGetCount() {
String str1 = \"cdabkkcadkabkebfkabkskab\";
String str2 = \"ab\";
int count = getCount(str1, str2);
System.out.println(count);
}
8.4获取两个字符串中最大相同子串。比如: str1 = \"abcwerthelloyuiodef“;str2 = \"cvhellobnm\" 提示:将短的那个串进行长度依次递减的子串与较长的串比较。
// 如果只存在一个最大长度的相同子串
public String getMaxSameSubString(String str1, String str2) {
if (str1 != null && str2 != null) {
String maxStr = (str1.length() > str2.length()) ? str1 : str2;
String minStr = (str1.length() > str2.length()) ? str2 : str1;
int len = minStr.length();
for (int i = 0; i < len; i++) {// 0 1 2 3 4 此层循环决定要去几个字符
for (int x = 0, y = len - i; y <= len; x++, y++) {
if (maxStr.contains(minStr.substring(x, y))) {
return minStr.substring(x, y);
}
}
}
}
return null;
}
// 如果存在多个长度相同的最大相同子串
// 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
public String[] getMaxSameSubString1(String str1, String str2) {
if (str1 != null && str2 != null) {
StringBuffer sBuffer = new StringBuffer();
String maxString = (str1.length() > str2.length()) ? str1 : str2;
String minString = (str1.length() > str2.length()) ? str2 : str1;
int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0, y = len - i; y <= len; x++, y++) {
String subString = minString.substring(x, y);
if (maxString.contains(subString)) {
sBuffer.append(subString + \",\");
}
}
System.out.println(sBuffer);
if (sBuffer.length() != 0) {
break;
}
}
String[] split = sBuffer.toString().replaceAll(\",$\", \"\").split(\"\\\\,\");
return split;
}
return null;
}
// 如果存在多个长度相同的最大相同子串:使用ArrayList
// public List<String> getMaxSameSubString1(String str1, String str2) {
// if (str1 != null && str2 != null) {
// List<String> list = new ArrayList<String>();
// String maxString = (str1.length() > str2.length()) ? str1 : str2;
// String minString = (str1.length() > str2.length()) ? str2 : str1;
//
// int len = minString.length();
// for (int i = 0; i < len; i++) {
// for (int x = 0, y = len - i; y <= len; x++, y++) {
// String subString = minString.substring(x, y);
// if (maxString.contains(subString)) {
// list.add(subString);
// }
// }
// if (list.size() != 0) {
// break;
// }
// }
// return list;
// }
//
// return null;
// }
@Test
public void testGetMaxSameSubString() {
String str1 = \"abcwerthelloyuiodef\";
String str2 = \"cvhellobnmiodef\";
String[] strs = getMaxSameSubString1(str1, str2);
System.out.println(Arrays.toString(strs));
}
8.5对字符串中字符进行自然顺序排序。 提示: 1)字符串变成字符数组。 2)对数组排序,选择,冒泡,Arrays.sort(); 3)将排序后的数组变成字符串。
@Test
public void testSort() {
String str = \"abcwerthelloyuiodef\";
char[] arr = str.toCharArray();
Arrays.sort(arr);
String newStr = new String(arr);
System.out.println(newStr);
}
StringBuffer、StringBuilder
1.String、StringBuffer、StringBuilder三者的对比
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
2.StringBuffer与StringBuilder的内存解析
以StringBuffer为例:
源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String(\"abc\");//char[] value = new char[]{\'a\',\'b\',\'c\'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append(\'a\');//value[0] = \'a\';
sb1.append(\'b\');//value[1] = \'b\';
StringBuffer sb2 = new StringBuffer(\"abc\");//char[] value = new char[\"abc\".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
3.对比String、StringBuffer、StringBuilder三者的执行效率
从高到低排列:StringBuilder > StringBuffer > String
4.StringBuffer、StringBuilder中的常用方法
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
*遍历:for() + charAt() / toString()
JDK 8之前时间日期API
1.获取系统当前时间
1.System类中的currentTimeMillis()
@Test
public void test1(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//称为时间戳
System.out.println(time);
}
2.java.util.Date类与java.sql.Date类
/*
java.util.Date类
|---java.sql.Date类
1.两个构造器的使用
>构造器一:Date():创建一个对应当前时间的Date对象
>构造器二:创建指定毫秒数的Date对象
2.两个方法的使用
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)
3. java.sql.Date对应着数据库中的日期类型的变量
如何实例化
如何将java.util.Date对象转换为java.sql.Date对象
*/
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019
System.out.println(date1.getTime());//1550306204104
//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
如何将java.util.Date对象转换为java.sql.Date对象
//情况一:
// Date date4 = new java.sql.Date(2343243242323L);
// java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
3.java.text.SimpleDataFormat类
SimpleDateFormat对日期Date类的格式化和解析
1.两个操作:
①格式化:日期 --->字符串
②解析:格式化的逆过程,字符串 ---> 日期
2.SimpleDateFormat的实例化
//*************按照指定的方式格式化和解析:调用带参的构造器*****************
//SimpleDateFormat sdf1 = new SimpleDateFormat(\"yyyyy.MMMMM.dd GGG hh:mm aaa\");
SimpleDateFormat sdf1 = new SimpleDateFormat(\"yyyy-MM-dd hh:mm:ss\");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date2 = sdf1.parse(\"2020-02-18 11:48:27\");
System.out.println(date2);
小练习:
/*
练习一:字符串\"2020-09-08\"转换为java.sql.Date
练习二:\"三天打渔两天晒网\" 1990-01-01 xxxx-xx-xx 打渔?晒网?
举例:2020-09-08 ? 总天数
总天数 % 5 == 1,2,3 : 打渔
总天数 % 5 == 4,0 : 晒网
总天数的计算?
方式一:( date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24) + 1
方式二:1990-01-01 --> 2019-12-31 + 2020-01-01 -->2020-09-08
*/
@Test
public void testExer() throws ParseException {
String birth = \"2020-09-08\";
SimpleDateFormat sdf1 = new SimpleDateFormat(\"yyyy-MM-dd\");
Date date = sdf1.parse(birth);
// System.out.println(date);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
4.Calendar类:日历类、抽象类
@Test
public void testCalendar(){
//1.实例化
//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.getClass());
//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
//set()
//calendar可变性
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime():日历类---> Date
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
}
JDK8中新时间日期API
1.日期时间API的迭代:
第一代:jdk1.0 Date类
第二代:jdk1.1 Calendar类,一定程度上替换Date类
第三代:jdk1.8 提出了新的一套API
2.前两代存在的问题举例:
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
3.java 8 中新的日期时间API涉及到的包
java.time – 包含值对象的基础包
java.time.chrono – 提供对不同的日历系统的访问
java.time.format – 格式化和解析时间和日期
java.time.temporal – 包括底层框架和扩展特性
java.time.zone – 包含时区支持的类
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽 管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
4.本地日期、本地时间、本地日期时间的使用:LocalDate / LocalTime / LocalDateTime
4.1 说明:
①LocalDate、LocalTime、LocalDateTime 类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区 相关的信息。
②LocalDateTime相较于LocalDate、LocalTime,使用频率要高
③类似于Calendar
4.2 常用方法:
方法 | 描述 |
---|---|
now() / * now(ZoneId zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth()/getDayOfYear() | 获得月份天数(1-31) /获得年份天数(1-366) |
getDayOfWeek() | 获得星期几(返回一个 DayOfWeek 枚举值) |
getMonth() | 获得月份, 返回一个 Month 枚举值 |
getMonthValue() / getYear() | 获得月份(1-12) /获得年份 |
getHour()/getMinute()/getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths()/minusWeeks()/minusDays()/minusYears()/minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
5.时间点:Instant
5.1 说明:
①时间线上的一个瞬时点。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数
②类似于Date类
5.2 常用方法:
方法 | 描述 |
---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的Instant类的对象 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个 OffsetDateTime |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳 |
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01 日08时00分00秒)起至现在的总秒数。
6.日期时间格式化类:DateTimeFormatter
6.1 说明:
①格式化或解析日期、时间
②类似于SimpleDateFormat
6.2 实例化方式
①预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
②本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
③自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
6.3常用方法:
方法 | 描述 |
---|---|
ofPattern(String pattern) | 静态方法 , 返 回 一 个 指 定 字 符 串 格 式 的 DateTimeFormatter |
format(TemporalAccessor t) | 格式化一个日期、时间,返回字符串 |
parse(CharSequence text) | 将指定格式的字符序列解析为一个日期、时间 |
特别的:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
//重点: 方式:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern(\"yyyy-MM-dd hh:mm:ss\");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse(\"2019-02-18 03:52:09\");
System.out.println(accessor);
7.其它API的使用 (不讲)
7.1 带时区的日期时间:ZonedDateTime / ZoneId
举例:
// ZoneId:类中包含了所的时区信息
@Test
public void test1(){
//getAvailableZoneIds():获取所的ZoneId
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for(String s : zoneIds){
System.out.println(s);
}
System.out.println();
//获取“Asia/Tokyo”时区对应的时间
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of(\"Asia/Tokyo\"));
System.out.println(localDateTime);
}
//ZonedDateTime:带时区的日期时间
@Test
public void test2(){
//now():获取本时区的ZonedDateTime对象
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
//now(ZoneId id):获取指定时区的ZonedDateTime对象
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of(\"Asia/Tokyo\"));
System.out.println(zonedDateTime1);
}
7.2 时间间隔:Duration--用于计算两个“时间”间隔,以秒和纳秒为基准
举例:
@Test
public void test3(){
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());
}
7.3 日期间隔:Period --用于计算两个“日期”间隔,以年、月、日衡量
举例:
@Test
public void test4(){
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);
}
7.4 日期时间校正器:TemporalAdjuster
举例:
@Test
public void test5(){
//获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println(localDateTime);
//获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster(){
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate)temporal;
if(date.getDayOfWeek().equals(DayOfWeek.FRIDAY)){
return date.plusDays(3);
}else if(date.getDayOfWeek().equals(DayOfWeek.SATURDAY)){
return date.plusDays(2);
}else{
return date.plusDays(1);
}
}
});
System.out.println(\"下一个工作日是:\" + localDate);
}
Java比较器
1.Java比较器的使用背景:
Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的
但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
2.自然排序:使用Comparable接口
2.1 说明
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3.重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
在compareTo(obj)方法中指明如何排序
2.2 自定义类代码举例:
public class Goods implements Comparable{
private String name;
private double price;
//指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
@Override
public int compareTo(Object o) {
// System.out.println(\"**************\");
if(o instanceof Goods){
Goods goods = (Goods)o;
//方式一:
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else{
// return 0;
return -this.name.compareTo(goods.name);
}
//方式二:
// return Double.compare(this.price,goods.price);
}
// return 0;
throw new RuntimeException(\"传入的数据类型不一致!\");
}
}
3.定制排序:使用Comparator接口
3.1 说明
①当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,
那么可以考虑使用 Comparator 的对象来排序②重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
3.2 代码举例:
Comparator com = new Comparator() {
//指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException(\"输入的数据类型不一致\");
}
}
使用:Arrays.sort(goods,com)//goods表示要排序的对象
4.两种排序方式对比
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
Comparator接口属于临时性的比较。
其他类
1.System类
①System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
②由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实 例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便 的进行调用。
1.1方法
native long currentTimeMillis():该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时 间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status):该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表 异常退出。
void gc():该方法的作用是请求系统进行垃圾回收。
String getProperty(String key):该方法的作用是获得系统中属性名为key的属性对应的值。
2.Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度
3.BigInteger类、BigDecimal类
说明:
①java.math包的BigInteger可以表示不可变的任意精度的整数
②要求数字精度比较高,故用到java.math.BigDecimal类
代码举例:
public void testBigInteger() {
BigInteger bi = new BigInteger(\"12433241123\");
BigDecimal bd = new BigDecimal(\"12435.351\");
BigDecimal bd2 = new BigDecimal(\"11\");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));//15为精度,即保留多少位小数
}
枚举类的使用
1.枚举类的说明:
1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
2.当需要定义一组常量时,强烈建议使用枚举类
3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
2.如何自定义枚举类?步骤:
//自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final的
public static final Season SPRING = new Season(\"春天\",\"春暖花开\");
public static final Season SUMMER = new Season(\"夏天\",\"夏日炎炎\");
public static final Season AUTUMN = new Season(\"秋天\",\"秋高气爽\");
public static final Season WINTER = new Season(\"冬天\",\"冰天雪地\");
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求1:提供toString()
@Override
public String toString() {
return \"Season{\" +
\"seasonName=\'\" + seasonName + \'\\\'\' +
\", seasonDesc=\'\" + seasonDesc + \'\\\'\' +
\'}\';
}
}s
3.jdk 5.0 新增使用enum定义枚举类。步骤:
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用\",\"隔开,末尾对象\";\"结束
SPRING(\"春天\",\"春暖花开\"),
SUMMER(\"夏天\",\"夏日炎炎\"),
AUTUMN(\"秋天\",\"秋高气爽\"),
WINTER(\"冬天\",\"冰天雪地\");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
4.使用enum定义枚举类之后,枚举类常用方法:(继承于java.lang.Enum类)
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
// System.out.println(Season1.class.getSuperclass());
System.out.println(\"****************\");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
System.out.println(\"****************\");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf(\"WINTER\");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf(\"WINTER1\");
System.out.println(winter);
5.使用enum定义枚举类之后,如何让枚举类对象分别实现接口:
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用\",\"隔开,末尾对象\";\"结束
SPRING(\"春天\",\"春暖花开\"){
@Override
public void show() {
System.out.println(\"春天在哪里?\");
}
},
SUMMER(\"夏天\",\"夏日炎炎\"){
@Override
public void show() {
System.out.println(\"宁夏\");
}
},
AUTUMN(\"秋天\",\"秋高气爽\"){
@Override
public void show() {
System.out.println(\"秋天不回来\");
}
},
WINTER(\"冬天\",\"冰天雪地\"){
@Override
public void show() {
System.out.println(\"大约在冬季\");
}
};
}
注解的使用
1.注解的理解
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射 + 设计模式
2.注解的使用示例
示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
3.如何自定义注解
参照@SuppressWarnings定义
说明:
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target
代码举例:
① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没有成员,表明是一个标识作用。
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default \"hello\";
}
4.元注解 :对现有的注解进行解释说明的注解。
元注解:对现有的注解进行解释说明的注解
①Retention:指定所修饰的 Annotation 的生命周期:SOURCE\\CLASS(默认行为)\\RUNTIME
只有声明为RUNTIME生命周期的注解,才能通过反射获取。
②Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
-----------出现的频率较低-----------
③Documented:表示所修饰的注解在被javadoc解析时,保留下来。
④Inherited:被它修饰的 Annotation 将具有继承性。
---> 类比:元数据的概念:String name = \"Tom\";
5.如何获取注解信息:
通过反射来进行获取、调用
前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME
6.JDK8中注解的新特性:可重复注解、类型注解
6.1 可重复注解:
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。6.2 类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
数组和集合
1.集合与数组存储数据概述:
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
2.数组存储的特点:
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
3.数组存储的弊端:
一旦初始化以后,其长度就不可修改。
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
4.集合存储的优点:
解决数据存储数据方面的弊端
Collection接口
1.单列集合框架结构
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组
|----ArrayList、LinkedList、Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet、LinkedHashSet、TreeSet
对应图示:
2.Collection接口常用方法:
add(Object obj)、addAll(Collection coll)、size()、isEmpty()、clear()
contains(Object obj)、containsAll(Collection coll)、remove(Object obj)、removeAll(Collection coll)、retainsAll(Collection coll)、equals(Object obj)
hashCode()、toArray()、iterator()
3.Collection集合与数组间的转换
集合 --->数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
拓展:数组 --->集合:调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{\"AA\", \"BB\", \"CC\"});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
4.使用Collection集合存储对象,要求对象所属的类满足:
向
Collection
接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
.
5.本章节对大家的要求:
层次一:选择合适的集合类去实现数据的保存,调用其内部的相关方法。
层次二:不同的集合类底层的数据结构为何?如何实现数据的操作的:增删改查等。
Iterator接口
1.遍历Collection的两种方式:
①使用迭代器Iterator ②foreach循环(增强for循环)
2.java.utils包下定义的迭代器接口:Iterator
2.1说明:
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
2.2作用:
遍历集合Collection元素
2.3如何获取实例:coll.iterator返回一个迭代器实例
coll.iterator返回一个迭代器实例
2.4遍历的代码实现:
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
2.5图示说明:
2.6 迭代器中的remove()的使用:
//测试Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,
//再调用remove都会报IllegalStateException。
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(\"Jerry\",20));
coll.add(new String(\"Tom\"));
coll.add(false);
//删除集合中\"Tom\"
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
// iterator.remove();
Object obj = iterator.next();
if(\"Tom\".equals(obj)){
iterator.remove();
// iterator.remove();
}
}
//遍历集合
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
3.增强for循环:(foreach循环)
3.1遍历集合举例:
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(\"Jerry\",20));
coll.add(new String(\"Tom\"));
coll.add(false);
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for(Object obj : coll){
System.out.println(obj);
}
}
3.2遍历数组举例:
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
System.out.println(i);
}
}
List接口
List接口是Collection子接口
1.存储的数据特点:
存储有序的可重复的数据
2.常用方法:(记住)
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
3.常用实现类:
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
4.源码分析:
4.1ArrayList的源码分析:
4.1.1 jdk7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
4.1.2 jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异。
4.1.3 小结
jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
4.2 LinkedList的源码分析:
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
4.3 Vector的源码分析:
Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
5.存储的元素的要求:
添加的对象所在的类要重写equals()
6.ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:见上(3、4)
Set接口
Set接口是Collection子接口
1.存储的数据特点:
无序不可重复
以HashSet为例说明:
1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
s2.不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
2.元素添加过程
以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况2对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下在jdk7的时候HashSet底层是数组+链表的结构
3.常用方法:
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
add(Object obj)、addAll(Collection coll)、size()、isEmpty()、clear()
contains(Object obj)、containsAll(Collection coll)、remove(Object obj)、removeAll(Collection coll)、retainsAll(Collection coll)、equals(Object obj)
hashCode()、toArray()、iterator()
4.常用实现类:
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历 在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|----TreeSet:可以按照添加对象的指定属性,进行排序。
5.存储对象所在类的要求:
5.1 HashSet、LinkedSet:
要求1:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求2:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
5.2 TreeSet:
1.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
2.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
6.TreeSet的使用:
6.1 使用说明:
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
6.2 常用的排序方式:
6.2.1 自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
//失败:不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add(\"AA\");
// set.add(new User(\"Tom\",12));
//举例一:
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
//举例二:
set.add(new User(\"Tom\",12));
set.add(new User(\"Jerry\",32));
set.add(new User(\"Jim\",2));
set.add(new User(\"Mike\",65));
set.add(new User(\"Jack\",33));
set.add(new User(\"Jack\",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
6.2.2 定制排序
@Test
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException(\"输入的数据类型不匹配\");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User(\"Tom\",12));
set.add(new User(\"Jerry\",32));
set.add(new User(\"Jim\",2));
set.add(new User(\"Mike\",65));
set.add(new User(\"Mary\",33));
set.add(new User(\"Jack\",33));
set.add(new User(\"Jack\",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
Map接口
双列集合框架:Map
1.常用实现类结构
|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
2.存储结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
图示:
3.常用方法:
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()
4.内存结构说明:(难点)
4.1 HashMap在jdk7中实现原理:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put...
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
4.2 HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1.new HashMap():底层没有创建一个长度为16的数组
2.jdk 8底层的数组是:Node[],而非Entry[]
3.首次调用put()方法时,底层创建长度为16的数组
4.jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
①形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
②当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
4.3 HashMap底层典型属性的属性的说明:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
4.4 LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层是用的结构与HashMap相同,因为LinkedHashMap继承与HashMap
区别在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node
HashMap源码中:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
LinkedHashMap源码中:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
5.TreeMap的使用
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序 、定制排序
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User(\"Tom\",23);
User u2 = new User(\"Jerry\",32);
User u3 = new User(\"Jack\",20);
User u4 = new User(\"Rose\",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + \"---->\" + entry.getValue());
}
}
//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException(\"输入的类型不匹配!\");
}
});
User u1 = new User(\"Tom\",23);
User u2 = new User(\"Jerry\",32);
User u3 = new User(\"Jack\",20);
User u4 = new User(\"Rose\",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + \"---->\" + entry.getValue());
}
}
6.使用Properties读取配置文件
public class PropertiesTest {
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream(\"jdbc.properties\");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty(\"name\");
String password = pros.getProperty(\"password\");
System.out.println(\"name = \" + name + \", password = \" + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Collections工具类的使用
Collections工具类
1.作用:
①Collections 是一个操作 Set、List 和 Map 等集合的工具类
②Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
③Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集 合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全 问题
2.常用方法:
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程安全的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)
泛型的理解
1.泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
2.泛型的引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就 是类型参数,即泛型。
泛型在集合中的使用
1.在集合中使用泛型之前的例子
//在集合中使用泛型之前的情况:
@Test
public void test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全
// list.add(\"Tom\");
for(Object score : list){
//问题二:强转时,可能出现ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
图示:
2.在集合中使用泛型例子1
//在集合中使用泛型的情况:以ArrayList为例
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
//编译时,就会进行类型检查,保证数据的安全
// list.add(\"Tom\");
//方式一:
// for(Integer score : list){
// //避免了强转操作
// int stuScore = score;
// System.out.println(stuScore);
// }
//方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
图示:
3.在集合中使用泛型例子2
//在集合中使用泛型的情况:以HashMap为例
@Test
public void test3(){
// Map<String,Integer> map = new HashMap<String,Integer>();
//jdk7新特性:类型推断
Map<String,Integer> map = new HashMap<>();
map.put(\"Tom\",87);
map.put(\"Jerry\",87);
map.put(\"Jack\",67);
// map.put(123,\"ABC\");
//泛型的嵌套
Set<Map.Entry<String,Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + \"----\" + value);
}
}
4.集合中使用泛型总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) --->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
自定义泛型类、泛型接口、泛型方法
1.举例:
1.1 Order.java
public class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order(){
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return \"Order{\" +
\"orderName=\'\" + orderName + \'\\\'\' +
\", orderId=\" + orderId +
\", orderT=\" + orderT +
\'}\';
}
//静态方法中不能使用类的泛型。
// public static void show(T orderT){
// System.out.println(orderT);
// }
public void show(){
//编译不通过
// try{
// }catch(T t){
// }
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
1.2 SubOrder.java
public class SubOrder extends Order<Integer> {//SubOrder:不是泛型类
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
//实例化时下面的情况是错误的
SubOrder<Integer> o = new SubOrder<>();
1.3 SubOrder1.java
public class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}
1.4 测试
public class GenericTest1 {
@Test
public void test1(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
Order order = new Order();
order.setOrderT(123);
order.setOrderT(\"ABC\");
//建议:实例化时指明类的泛型
Order<String> order1 = new Order<String>(\"orderAA\",1001,\"order:AA\");
order1.setOrderT(\"AA:hello\");
}
@Test
public void test2(){
SubOrder sub1 = new SubOrder();
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
sub1.setOrderT(1122);
SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT(\"order2...\");
}
@Test
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值。
// list1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
}
2.注意点:
1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: <E1,E2,E3>
2.泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass(){}
3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4.泛型不同的引用不能相互赋值。
>尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有 一个ArrayList被加载到JVM中。
5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一律都用。要不用,一律都不要用。
6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7.jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
8.泛型的指定中不能使用基本数据类型,可以使用包装类替换
9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。
10.异常类不能是泛型的
11.不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
1)子类不保留父类的泛型:按需实现
①没有类型 擦除
②具体类型
2)子类保留父类的泛型:泛型子类
① 全部保留
② 部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
3.应用场景举例:
3.1 DAO.java:定义了操作数据库中表的通用操作
体现了ORM思想(数据库中的表和Java中的类对应)
public class DAO<T> {//表的共性操作的DAO
//添加一条记录
public void add(T t){
}
//删除一条记录
public boolean remove(int index){
return false;
}
//修改一条记录
public void update(int index,T t){
}
//查询一条记录
public T getIndex(int index){
return null;
}
//查询多条记录
public List<T> getForList(int index){
return null;
}
//泛型方法
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
return null;
}
}
3.2 CustomerDAO.java
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
3.3 StudentDAO.java
public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}
泛型在继承上的体现
泛型在继承上的体现:
虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A 是 B 的父类
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//编译不通过
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具有子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。出错。
*/
show(list1);
show1(list2);
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
通配符
1.通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
@Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//
List<String> list3 = new ArrayList<>();
list3.add(\"AA\");
list3.add(\"BB\");
list3.add(\"CC\");
list = list3;
//添加(写入):对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add(\"DD\");
// list.add(\'?\');
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
2.涉及通配符的集合的数据的写入和读取:
见上
3.有限制条件的通配符的使用
? extends A:
G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
////编译不通过
// Person obj = list2.get(0);
//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}
File类的使用
1.File类的理解
1.File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
2.File类声明在java.io包下
3.File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
4.后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的\"终点\".
2.File的实例化
2.1 常用构造器
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
2.2 路径的分类
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径以idea为例:
如果使用Junit单元测试方法,那么相对路径即为当前Module下,
如果使用的是main方法,那么相对路径即为当前的Project路径下
Eclipse:
上述的两种方法,相对路径都是当前Project下
2.3 路径分隔符
路径分隔符和系统有关:
Windows和DOS系统默认使用“
\\
”来表示 UNIX和URL使用“
/
”来表示Java程序支持跨平台运行,因此路径分隔符要慎用。
为了解决这个隐患,File类提供了一个常量: public static final String separator。根据操作系统,动态的提供分隔符。
File file1 = new File(\"d:\\atguigu\\info.txt\"); File file2 = new File(\"d:\" + File.separator + \"atguigu\" + File.separator + \"info.txt\"); File file3 = new File(\"d:/atguigu\");
3.File类的常用方法
3.1 File类的获取功能
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
3.2 File类的重命名功能
//public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
@Test
public void test4(){
File file1 = new File(\"hello.txt\");
File file2 = new File(\"D:\\\\io\\\\hi.txt\");
boolean renameTo = file2.renameTo(file1);
System.out.println(renameTo);
}
3.3 File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
3.4 File类的创建功能
创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目
路径下。
3.5 File类的删除功能
删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
IO流概述
归根结底是因为没有理解JavaIO框架的设计思想:
可以沿着这条路想一想:
1,学IO流之前,我们写的程序,都是在内存里自己跟自己玩。比如,你声明个变量,创建个数组,创建个集合,写一个排序算法,模拟一个链表,使用一些常用API,现在回想一下,是不是在只是自己在内存里玩一玩?计算机组成包括运算器,控制器,存储器,输入设备,输出设备。那么你前面的工作,仅仅够你的程序和内存以及CPU打打交道,如果你需要操作外部设备呢?比如键盘,显示器,再比如,最常见的外设:硬盘?甚至未来世界里的每家每户都有的机器人,“如何让你的程序和机器人进行交互呢?”
2,所以程序设计语言必须要提供程序与外部设备交互的方式,这就是IO框架的由来。我们需要和外部设备进行数据的交互。那么,计算机是通过什么和外部进行交互的呢?很简单就能想到:数据线。数据线里传播的是什么呢?一个词:比特流。比特就是bit的谐音,计算机中“位”的意思,代表0或1。1位或者1bit,就是一个0或一个1。但是,毕竟0或1不能表示什么,所以计算机更常见的基本单位是字节,也就是用8位0或1组成的一段数据。以上是对比特流的由来做一个简单地解释。(比特流一词来自于计算机网络原理中,对物理层传输内容的描述:物理层(网线)中传输的是“比特流”,在这里借用这个名词代指数据的表示形式,帮助理解)
上面两段话的意思,其实是为了下文做铺垫,帮助理解输入输出最重要的概念:方向性。输入还是输出,是相对于程序或者说相对于内存而言的。数据从外流到内存,就是输入(读),数据从内存出去,就是输出(写)。
3,既然计算机和外界进行信息的输入和输出交互,用的是比特流,那么很容易就能想到IO流名字的由来了。就是比喻输入输出的数据像流一样。我们可以这么认为,任何外部设备与内存之间输入输出的操作,都是需要输入输出流(IO流)来完成的,这里的IO流,指的就是比特流(或者称字节流)。这些外部设备,包括,键盘(标准输入设备),显示器(标准输出设备),音响,网络上另一台主机,甚至你玩游戏用的游戏手柄,以及各种各样的信号传感器,都可以叫做外部设备,和这些设备之间进行数据交互,显然不可能靠之前学习的那些数组,集合,常用类,String等等来完成。而是要靠和外界数据交换的类来完成。靠什么来进行数据交换,就是前面说的,比特流,或者说IO流类。
4,那么,既然要学习IO流,就得针对某一个输入输出设备来学习。哪种输入输出设备最重要同时也最常见?当然是硬盘。硬盘在这里的含义也可以理解为文件系统。(Java程序是运行在某操作系统平台上的应用软件JVM上的,实际上Java程序可见的并不是硬盘,而是操作系统提供的文件系统,因此此处可直接理解为文件系统)。因此,我们学习IO流的时候,基本上是学习的Java如何操作文件系统,除了文件系统,我们还能够了解Java操作标准输入输出设备,如http://System.in和System.out。
5,知道了学习的方向,是要使用Java操作文件系统,那么首先要学习的就是文件的表示,即File类。然后,我们要操作做文件,虽然我们大部分操作都是操作文件系统,但是要明白IO流的概念不仅仅局限在操作文件上,前面我已经提到了,我们的编程语言是要能操作所有的输入输出,因此,API提供了两个顶层抽象类,用来表示操作所有的输出输出:InputStream,OutputStream。并且,这两个类表示字节的输入输出,因为输入输出的本质是字节流。这里注意体会一句话“字节流是最最基本的流”,这句话的由来就是因为计算机底层传递的就是字节。那么,当我们要操作文件的时候,就需要具体的对文件系统操作的IO实现类,于是我们需要学习FileInputStream和FileOutputStream,它们是文件输入输出字节流。这里之所以FileInputStream/OutputStream作为子类出现,按照面向对象思想理解就是,将来还有别的字节流来操作别的设备(比如将来需要通过操作网络设备获取网络数据,再比如需要操作机器人,那么或许就会再来个RobotInputStream和RobotOutputStream,这些新的需求也就都可以继承这个体系)
(这里顺便提一句架构设计思想,其中有一种设计原则叫“开闭原则”,其核心是:一个对象对扩展开放,对修改关闭。就是说,一旦写好了某个类,就不要去轻易改动他,而是要保证它一直能运行下去,而面对新的功能需求时,只要在原有代码上增加即可,而不是修改原有代码。要做到开闭原则,就需要分清需求中未来哪些部分是稳定的,哪些是很可能变化的,而往往抽象的部分是最稳定的,把稳定的内容分离出来,就能满足开闭原则。这就是为什么Java的类设计的如此之琐碎,为什么我们要从继承关系角度去理解JavaIO流的设计)
6,学了文件IO字节流之后,我们会发现原始的字节流对象用起来没那么高效,因为每个读或写请求都由底层操作系统处理,这些请求往往会触发磁盘访问、网络活动或其他一些相对昂贵的操作。不带缓冲区的流对象,只能一个字节一个字节的读,每次都调用底层的操作系统API,非常低效,而带缓冲区的流对象,可以一次读一个缓冲区,缓冲区空了才去调用一次底层API,这就能大大提高效率。所以又有了BufferedInputStream和BufferedOutputSteam,他们的用法是把字节流对象传入后再使用,也相当于把它俩套在了字节流的外面,给字节流装了个“外挂”,让基本字节流如虎添翼。
7,说到操作文件,就不得不提到文件的分类和编码格式。文件分为二进制文件和文本文件,二进制文件是用记事本打开后看不懂的,他们的编码格式是特殊的,比如pdf文件,exe文件。记事本打开后人能看懂的只有纯文本文件,我们处理文件(或者说处理任何的字节流),就免不了处理一些文本文件(或文本字节流)。如果是英语国家的人还好说,因为他们是用的常用字符用一张ASCII码表就能表示得出来,用一个字节就能表示一个字母。但是显然,对非英语国家的人来说,一个字节的大小无法表示他们所有的文字。因此,人们需要有能够处理字符的类,或者说这个类提供一个功能:就是把输入的字节转成字符,把要输出的字符转成计算机可以识别的字节。所以,你需要两个转换流:InputStreamReader和OutputStreamWriter。这两个类的作用分别是把字节流转成字符流,把字符流转成字节流。但是这两个流需要套在现成的字节流上才能使用,当中用到的设计模式也就是常说的装饰模式。当字节流被转成字符流之后,恭喜你,你可以不必操作字节流了,而是可以用人类的方式read和write各种“文字”。
(那么,我们为什么还要学习字节流?因为字节流依然有它的作用范围。首先,所有的流都是建立在字节流之上的,比如字符流。字节流或许可以读任何字节,但是他处理不了Unicode(万国码),他处理不了Data流,Object流,也就是说,它做不了高级的事情,只能读写最原始的东西。字节流好比动物,能看,能听,能汪汪叫,但是他不能读书,不能写字,不能理解更高级的知识。其次要注意的是,字符流只能用来处理文本文件,也就是只能来处理字符,如果出来用来处理二进制文件,会带来错误,所以处理二进制文件只能用字节流)
8,还是回到文件系统,我们最常见的是和文件系统打交道,那么针对如此常见的用途,读取文本文件能不能用一种方便的方式呢?当然,大牛们替你想到并提供了。FileReader和FileWriter这两个流对象可以直接把文件转成读取、写入流。让你省去了创建字节流,再套上转换流的步骤。看看这类名起的,实际上很形象,xxxReader和xxxWriter,明摆着告诉你“阅读和书写”都是“人可以做的”也就是他们表示的是字符流。同理上面的InputStreamReader和OutputStreamWriter,表示的是把字节流转成人可读的,把字节流转成人可写的。因此他们的顶层抽象类:Reader和Writer,表示的是所有人类可读可写的字符流统称。
10,同上面说的缓冲区的作用,再把Reader和Writer做成高效的,就需要BufferedReader和BufferedWriter,把它们套在Reader和Writer上,就能实现高效的字符流。
11,讲到这里,IO流的大概思想已经说的的差不多了,是不是觉得之前混乱的那些类,现在知道他们的作用和设计思想以后,稍稍清晰了许多呢?可以简单的记,字节流是基础,理论上可用于所有的输入输出场景,内容是文字的字节流可以通过转换流转成字符流,转换流是字节流和字符流之间相互转换的桥梁,把字节流转成字符流,离不开转换流,字符流是对于字符功能的增强可用来处理“文字”,操作文件系统应用范围最广,所以JDK提供了现成的FileXXX类,用来方便编程使用。另外,还有许多类是“在内存里自己和自己玩的”比如ByteArrayReader/Writer,PipedWriter/Reader,它们虽然也称为“流对象”但是他们的数据不出内存,所以它们的close()方法可有可无。以及其他带有某些功能的类,比如序列化流,比如数据输入输出流,等等。IO流对象的用法和作用大同小异,其使用环境和意义取决于具体需要,用到了再具体分析即可。
这里先写到这了。这里作为Java编程入门应该可以了,主要介绍了JavaIO框架的设计思想,但具体底层实现细节,还需要学习JVM相关知识,以及微机原理和接口技术等等底层的课程。
之前各章讲述的操作都是发生在内存之中的,包括各种用来存数据的集合,一旦电脑关闭,或者程序关闭,哪些变量,数据都会随之消失。要想把数据永久性的保存,也就是存到我们的磁盘上,就需要IO技术,即 INPUT/OUTPUT (输入/输出);
1.流的分类
1.操作数据单位:字节流、字符流
2.数据的流向:输入流、输出流
3.流的角色:节点流、处理流
图示:
2.流的体系结构
3.重点说明的几个流结构
抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
InputStream
FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
OutputStream
FileOutputStream (write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
Reader
FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
Writer
FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()
4.输入、输出的标准化过程
4.1 输入过程
1.文件的实例化:创建File类的对象,指明读取的数据的来源。(要求此文件必须存在)
2.创建相应的输入流,将File类的对象作为参数,传入流的构造器中
3.具体的读入过程:创建相应的byte[] 或 char[]
4.关闭流资源:调用流的close()
程序中的异常需要使用
try-catch-finally
处理
4.2 输出过程
1.文件的实例化:创建File类的对象,指明输出的数据的位置。(不要求此文件一定存在)
2.创建相应的输出流,将File类的对象作为参数,输出流的构造器中
3.具体的读入过程:
write(byte[] 或 char[],0,len)
4.关闭流资源:调用流的close()
程序中的异常需要使用try-catch-finally处理
节点流(或文件流)
1.FileReader/FileWriter的使用:
1.1 FileReader的使用
将day09下的hello.txt文件内容读入程序中,并输出到控制台
说明点:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException。
@Test
public void testFileReader1() {
FileReader fr = null;
try {
//1.File类的实例化
File file = new File(\"hello.txt\");
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
//方式一:
//错误的写法
// for(int i = 0;i < cbuf.length;i++){
// System.out.print(cbuf[i]);
// }
//正确的写法
// for(int i = 0;i < len;i++){
// System.out.print(cbuf[i]);
// }
//方式二:
//错误的写法,对应着方式一的错误的写法
// String str = new String(cbuf);
// System.out.print(str);
//正确的写法
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr != null){
//4.资源的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.2 FileWriter的使用:
从内存中写出数据到硬盘的文件里。
说明:
1.输出操作,对应的File可以不存在的。并不会报异常
2.File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
@Test
public void testFileWriter() {
FileWriter fw = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File(\"hello1.txt\");
//2.提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file,false);
//3.写出的操作
fw.write(\"I have a dream!\\n\");
fw.write(\"you need to have a dream!\");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.3 文本文件的复制:
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File(\"hello.txt\");
File destFile = new File(\"hello2.txt\");
//不能使用字符流来处理图片等字节数据
// File srcFile = new File(\"爱情与友情.jpg\");
// File destFile = new File(\"爱情与友情1.jpg\");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入和写出操作
char[] cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符的个数
while((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
//方式一:
// try {
// if(fw != null)
// fw.close();
// } catch (IOException e) {
// e.printStackTrace();
// }finally{
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
//方式二:
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.FileInputStream / FileOutputStream的使用:
结论:
1.对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
2.对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
/*
实现对图片的复制操作
*/
@Test
public void testFileInputOutputStream() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//1.造文件
File srcFile = new File(\"onw.jpg\");
File destFile = new File(\"two.jpg\");
//2.造流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//3.复制的过程
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
//4.流资源的关闭
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.相对路径在IDEA和Eclipse中使用的区别?
IDEA:如果使用
单元测试
方法,相对路径基于当前Moudle
的 如果使用
main()
方法,相对路径是就与当前Project
的Eclipse:单元测试方法和main()方法的相对路径是基于
Project
的
缓冲流的使用
1.缓冲流涉及到的类:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
2.作用:
提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区,默认情况为8kb
3.典型代码
3.1 使用BufferedInputStream和BufferedOutputStream:
//实现文件复制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
3.2 使用BufferedReader和BufferedWriter:
/*
使用BufferedReader和BufferedWriter实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File(\"dbcp.txt\")));
bw = new BufferedWriter(new FileWriter(new File(\"dbcp1.txt\")));
//读写操作
//方式一:使用char[]数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
// bw.write(data + \"\\n\");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流的使用
1.转换流涉及到的类:
属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 --->字符数组、字符串
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
编码:字符数组、字符串 ---> 字节、字节数组
说明:编码决定了解码的方式
2.作用:
提供字节流与字符流之间的转换
3.图示:
4.典型实现:
@Test
public void test1(){
InputStreamReader isr = null;
try {
FileInputStream fis = new FileInputStream(\"dbcp.txt\");
isr = new InputStreamReader(fis,\"utf-8\");
char[] cbuf = new char[30];
int len;
while ((len = isr.read(cbuf)) != -1){
//String(char[] value, int offset,int count):
// 使用char数组中从offset索引开始,取count个字符来创建一个字符串对象
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (isr != null){
try {
isr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/*
通过缓冲流InputStreamReader、OutputStreamWriter将utf-8格式的文本文件输出为gbk格式的文本文件
*/
@Test
public void test2(){
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
FileInputStream fis = new FileInputStream(\"dbcp.txt\");
FileOutputStream fos = new FileOutputStream(\"dbcp_gbk.txt\");
isr = new InputStreamReader(fis,\"utf-8\");
osw = new OutputStreamWriter(fos,\"gbk\");
char[] cbuf = new char[30];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (osw != null){
try {
osw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (isr != null){
try {
isr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5.说明:
文件编码的方式(比如:gbk),决定了解码时使用的字符集(也之能是gbk)
编码集
1.常见的编码表
2.对后面学习的启示
客户端/浏览器端 <-----> 后台(Java,Go,Python,Node js,php) <-----> 数据库
为了保证不乱码,要求使用的字符集要统一:UTF-8
其它的流的使用
1.标准的输入输出流:
System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出
1.1修改默认的输入和输出行为:
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
2.打印流:
PrintStream 和PrintWriter
2.1说明:
3.数据流:
DataInputStream 和 DataOutputStream
3.1作用:
用于读取或写出基本数据类型的变量或字符串
3.2示例代码:
将内存中的字符串、基本数据类型的变量写出到文件中。
@Test
public void test1() {
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream(\"data.txt\"));
dos.writeUTF(\"BEEA\");
dos.flush();
dos.writeChar(\'女\');
dos.flush();
dos.writeBoolean(false);
dos.flush();
dos.writeInt(21);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
@Test
public void test2() {
DataInputStream dis = null;
String name;
char sex;
boolean b;
int age;
try {
dis = new DataInputStream(new FileInputStream(\"data.txt\"));
name = dis.readUTF();
sex = dis.readChar();
b = dis.readBoolean();
age = dis.readInt();
System.out.println(\"name = \" + name);
System.out.println(\"sex = \" + sex);
System.out.println(\"b = \" + b);
System.out.println(\"age = \" + age);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象流的使用
1.对象流:
ObjectInputStream 和 ObjectOutputStream
2.作用:
序列化过程:ObjectInputStream:内存中的对象转化成本地存储中的文件或者通过网络传输出去
反序列化过程ObjectOutputStream:序列化的逆过程,将本地存储的文件或从网络接收过来转换成内存中的对象
3.对象的序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
4.序列化代码实现:
@Test
public void testObjectOutput(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(\"object.dat\"));
oos.writeObject(new String(\"我爱码代码!\"));
oos.flush();
oos.writeObject(new Person(\"BEEA\",21));
oos.flush();
oos.writeObject(new Person(\"憨憨\",21,new Account(5000)));
oos.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5.反序列化代码实现:
@Test
public void testObjectInput(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(\"object.dat\"));
Object o = ois.readObject();
String str = (String) o;
Person p = (Person) ois.readObject();
Person p1 = (Person) ois.readObject();
System.out.println(str);
System.out.println(p);
System.out.println(p1);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
if (ois == null) {
try {
ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
6.实现序列化的对象所属的类需要满足:
1.需要实现接口:Serializable
2.当前类提供一个全局常量:serialVersionUID
3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
RandomAccessFile的使用
1.随机存取文件流:
RandomAccessFile
2.使用说明:
1.
RandomAccessFile
直接继承于java.lang.Object
类,实现了DataInput
和DataOutput
接口2.
RandomAccessFile
既可以作为一个输入流,又可以作为一个输出流3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
4.可以通过相关的操作,实现
RandomAccessFile
“插入”数据的效果
3.典型代码:
典型代码1:
@Test
public void test1(){
RandomAccessFile raf = null;
RandomAccessFile raf1 = null;
try {
raf = new RandomAccessFile(new File(\"one.jpg\"),\"r\");
raf1 = new RandomAccessFile(\"two.jpg\",\"rw\");
byte[] buffer = new byte[1024];
int len;
while ((len = raf.read(buffer)) != -1){
raf1.write(buffer,0,len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (raf1 != null) {
try {
raf1.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
典型代码2:
/*
使用RandomAccessFile实现数据的插入效果
*/
@Test
public void test3(){
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(\"hello.txt\",\"rw\");
raf.seek(3);//将指针调到角标为3的位置
//保存指针3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File(\"hello.txt\").length());
byte[] buffer = new byte[1024];
int len;
while ((len = raf.read(buffer)) != -1){
builder.append(new String(buffer,0,len));
}
//调回指针,写入“xyz”
raf.seek(3);
raf.write(\"xyz\".getBytes());
//将StringBuilder中的数据写入到文件中
raf.write(builder.toString().getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
Path、Paths、Files的使用
1.NIO的使用说明:
Java
NIO
(New IO,Non-Blocking IO)是从Java 1.4
版本开始引入的一套新 的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。
NIO将以更加高效的方式进行文件的读写操作。
随着
JDK7
的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为NIO.2
。
2.Path的使用 ---jdk7提供
2.1Path的说明:
使用Path替换原有的File类
2.2如何实例化:
Paths 类提供的静态 get() 方法用来获取 Path 对象:
①static Path get(String first, String … more) : 用于将多个字符串串连成路径
②static Path get(URI uri): 返回指定uri对应的Path路径
2.3常用方法:
3.Files工具类 ---jdk7提供
3.1作用:
java.nio.file.
Files
用于操作文件或目录的工具类。
3.2常用方法:
InetAddress类的使用
1.实现网络通信需要解决的两个问题
1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
2.找到主机后如何可靠高效地进行数据传输
2.网络通信的两个要素:
1.对应问题一:IP和端口号
2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
3.通信要素一:IP和端口号
3.1IP的理解
1.IP:唯一的标识 Internet 上的计算机(通信实体)
2.在Java中使用
InetAddress
类代表IP3.IP分类:
IPv4
和IPv6
; 万维网 和 局域网4.域名: www.baidu.com www.mi.com www.sina.com www.jd.com www.vip.com
域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(
DNS
) 负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析5.本地回路地址:127.0.0.1 对应着:localhost
3.2InetAddress类:
3.2.1实例化
InetAddress类的一个对象代表着一个具体的IP地址
获取实例的两个方法:getByName(String host) 、 getLocalHost()
3.2.2常用方法
两个常用方法:getHostName() / getHostAddress()
3.3端口号
端口号:正在计算机上运行的进程。
要求:不同的进程有不同的端口号
范围:被规定为一个 16 位的整数 0~65535。
端口号与IP地址的组合得出一个网络套接字:Socket
4.通信要素二:网络通信协议
4.1分型模型
4.2TCP和UDP的区别
4.3TCP三次握手和四次挥手
TCP网络编程
1.过程
2.客户端发送信息给服务端,服务端将数据显示在控制台上
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
InetAddress inet = null;
try {
inet = InetAddress.getByName(\"127.0.0.1\");
socket = new Socket(inet, 8899);
os = socket.getOutputStream();
os.write(\"服务端MM你好,我是客户端GG。\".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
ss = new ServerSocket(8899);
socket = ss.accept();
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println(baos);
System.out.println(\"接收到了来自于:\" + socket.getInetAddress().getHostAddress() + \"的数据。\");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
3:客户端发送文件给服务端,服务端将文件保存在本地。
@Test
public void client(){
OutputStream os = null;
FileInputStream fis = null;
Socket socket = null;
try {
socket = new Socket(\"127.0.0.1\",8899);
fis = new FileInputStream(\"one.jpg\");
os = socket.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server(){
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
try {
ss = new ServerSocket(8899);
socket = ss.accept();
is = socket.getInputStream();
fos = new FileOutputStream(\"three.jpg\");
byte[] buffer = new byte[10];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
@Test
public void client() {
OutputStream os = null;
FileInputStream fis = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
socket = new Socket(\"127.0.0.1\", 8899);
fis = new FileInputStream(\"hi.txt\");
os = socket.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//关闭数据的输出
socket.shutdownOutput();
//接收来自于服务器端的数据,并显示到控制台上
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
int len1;
while ((len1 = is.read(buffer)) != -1){
baos.write(buffer,0,len1);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
ss = new ServerSocket(8899);
socket = ss.accept();
is = socket.getInputStream();
fos = new FileOutputStream(\"hi1.txt\");
byte[] buffer = new byte[10];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println(\"图片传输已完成\");
//服务器端给予客户端反馈
os = socket.getOutputStream();
os.write(\"图片已收到\".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP网络编程
1.流程
1.DatagramSocket与DatagramPacket
2.建立发送端,接收端
3.建立数据包
4.调用Socket的发送、接收方法
5.关闭Socket
发送端与接收端是两个独立的运行程序
2.代码示例:
//发送端
@Test
public void sender(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String str = \"东风导弹已发射!\";
InetAddress inet = InetAddress.getLocalHost();
byte[] data = str.getBytes();
DatagramPacket packet = new DatagramPacket(data,data.length,inet,9090);
socket.send(packet);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (socket != null) {
socket.close();
}
}
}
//接收端
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data,0,data.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
}
URL编程
1.URL(Uniform Resource Locator)的理解:
URL(Uniform Resource Locator)
:统一资源定位符,它表示Internet 上某一 资源
的地址。
2.URL的5个基本结构:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
3.如何实例化:
URL url = new URL(\"https://localhost:8080/one.jpg?username=Tom\");
4.常用方法:
5.可以读取、下载对应的url资源:
public class URLTest1 {
public static void main(String[] args) {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL(\"http://localhost:8080/examples/one.jpg\");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream(\"day10\\\\four.jpg\");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println(\"下载完成\");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}
反射的概述
1.关于反射的理解
Reflection(反射)
是被视为动态语言
的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
2.体会反射机制的“动态性”
/*
动态性
*/
@Test
public void test2(){
String classPath = \"\";
int random = (int) (Math.random() * 3);
switch (random){
case 1:
classPath = \"java.util.Date\";
break;
case 2:
classPath = \"java.lang.Object\";
break;
case 3:
classPath = \"com.hanhan.java.Person\";
break;
}
try {
Object instance = getInstance(classPath);
System.out.println(instance);
} catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
e.printStackTrace();
}
}
public Object getInstance(String classPath) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
3.反射机制能提供的功能
4.相关API
java.lang.Class:反射的源头
Class类的理解与获取Class的实例
1.Class类的理解
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载
。加载到内存中的类,我们就称为运行时类
,此运行时类,就作为Class的一个实例。2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类。
2.获取Class实例的几种方式:
前三种多看看
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName(\"com.hanhan.java.Person\");
// clazz3 = Class.forName(\"java.lang.String\");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass(\"com.hanhan.java.Person\");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
3.总结:创建类的对象的方式?
①:new + 构造器
②:查看Xxx、Xxxs、XxxFactory、XxxBuilder类中是否含有静态方法的存在。可以调用静态方法创建Xxx对象
③:通过反射
4.Class实例可以是哪些结构的说明
了解ClassLoader
1.类的加载过程----了解
2.类的加载器的作用
3.类的加载器的分类
4.Java类编译、运行的执行的流程
5.使用Classloader加载src目录下的配置文件
Properties pros = new Properties();
//方式一:
//默认文件位置在当前module下
//FileInputStream fis = new FileInputStream(\"jdbc.properties\");
//pros.load(fis);
//方式二:使用ClassLoader读取配置文件
//此方式的默认文件位置是在当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream(\"jdbc1.properties\");
pros.load(is);
String user = pros.getProperty(\"user\");
String password = pros.getProperty(\"password\");
System.out.println(\"user = \" + user + \",password = \" + password);
反射应用一:创建运行时类的对象
1.说明
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器
2.空参的构造器的访问权限得够。通常,设置为public。在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
2.代码举例
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);
反射应用二:获取运行时类的完整结构
我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。。。。
典型代码:
1.属性
/*
getFields():获取运行时类及其所有父类权限为public的所有属性
*/
@Test
public void test1(){
Class<Person> clazz = Person.class;
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}
}
/*
getDeclaredFields():获取当前运行时类的所有属性(不包含父类的属性)
*/
@Test
public void test2(){
Class<Person> clazz = Person.class;
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
}
/*
权限修饰符 数据类型 变量名
*/
@Test
public void test3(){
Class<Person> clazz = Person.class;
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
//权限修饰符
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + \"\\t\");
//数据类型
Class type = f.getType();
System.out.print(type.getName() + \"\\t\");
//变量名
String name = f.getName();
System.out.print(name);
System.out.println();
}
}
2.方法
/*
@Xxxxx
权限修饰符 返回值类型 方法名(参数类型1 形参1,参数类型2 形参2) throws XxxException {}
*/
@Test
public void test3(){
Class<Person> clazz = Person.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
//获取方法中的注解
Annotation[] annos = m.getAnnotations();
for (Annotation a : annos) {
System.out.println(a);
}
//获取方法中的权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + \"\\t\");
//获取方法中的返回值类型
System.out.print(m.getReturnType() + \"\\t\");
//获取方法中的方法名
System.out.print(m.getName());
//获取方法中的形参列表
System.out.print(\"(\");
Class[] parameterTypes = m.getParameterTypes();
if (parameterTypes.length != 0){
for(int i = 0;i < parameterTypes.length ;i++){
if (i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + \" args_\" + i);
break;
}
System.out.print(parameterTypes[i].getName() + \" args_\" + i + \",\");
}
}
System.out.print(\")\");
//获取方法中的异常列表
Class[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length != 0){
System.out.println(\" throws \");
for (int i = 0;i < exceptionTypes.length ;i++){
if (i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
}
System.out.print(exceptionTypes[i].getName() + \",\");
}
}
System.out.println(\"{\\n\\t\");
System.out.println(\"}\");
}
}
3.构造器
/*
获取构造器结构
*/
@Test
public void test1(){
Class<Person> clazz = Person.class;
//getConstructors():获取当前运行时类声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor cons : constructors) {
System.out.println(cons);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor cons : declaredConstructors) {
System.out.println(cons);
}
}
4.运行时类的父类
/*
获取运行时类的父类
*/
@Test
public void test2(){
Class<Person> clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
5.运行时类的带泛型的父类
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
6.运行时类的带泛型的父类的泛型
/*
获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4(){
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
7.其他
/*
获取运行时类的带泛型的接口
*/
@Test
public void test5(){
Class<Person> clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println();
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c : interfaces1) {
System.out.println(c);
}
}
/*
获取运行时类所在的包
*/
@Test
public void test6(){
Class<Person> clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations){
System.out.println(a);
}
}
反射应用三:调用运行时类的指定结构
调用指定的属性:
/*
操作运行时类的指定属性
*/
@Test
public void tesField1() throws Exception{
Class<Person> clazz = Person.class;
//创建一个运行时类的实例
Person p = clazz.newInstance();
Field name = clazz.getDeclaredField(\"name\");
//保证当前属性是可以访问的
name.setAccessible(true);
/*
修改运行时类的属性的值
set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
*/
name.set(p,\"BEEA\");
/*
获取当前属性内容
get():参数1:获取哪个对象的当前属性值
*/
String pName = (String) name.get(p);
System.out.println(pName);
}
调用指定的方法:
/*
操作运行时类的指定方法
*/
@Test
public void testMethod() throws Exception {
Class<Person> clazz = Person.class;
//创建一个运行时类的实例
Person p = clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod(\"show\", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3.调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object chn = show.invoke(p, \"CHN\");
System.out.println(chn);
System.out.println(\"=====调用静态方法=====\");
Method showDesc = clazz.getDeclaredMethod(\"showDesc\");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object invoke = showDesc.invoke(Person.class);
System.out.println(invoke);
}
调用指定的构造器:
/*
操作运行时类的指定构造器
*/
@Test
public void testConstructor() throws Exception {
Class<Person> clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor<Person> cons = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
cons.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person person = cons.newInstance(\"Tom\");
System.out.println(person);
}
反射应用四:动态代理
1.代理模式的理解:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
2.静态代理:
//代理类和被代理类要实现的接口
public interface ClothFactory {
void produceCloth();
}
//代理类
public class ClothProxyFactory implements ClothFactory{
private ClothFactory factory;
public ClothProxyFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println(\"生产衣服的准备工作!\");
factory.produceCloth();
System.out.println(\"生产衣服的收尾工作!\");
}
}
//被代理类
public class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println(\"NIKE生产衣服!\");
}
}
//测试类
public class StaticProxyTest {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ClothProxyFactory clothProxyFactory = new ClothProxyFactory(nike);
clothProxyFactory.produceCloth();
}
}
静态代理的缺点:
代理类和目标 对象的类都是在编译期间确定下来,不利于程序的扩展。
每一个代 理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
3.动态代理:
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序
运行时根据需要动态创建
目标类的代理对象。
4.动态代理的实现:
4.1 需要解决的两个问题
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
(通过InvocationHandler接口的实现及其方法invoke())
4.2 代码
package com.atguigu.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 动态代理的举例
* @author hanhan
* @date 2022/8/11 20:05
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return \"I believe I can fly!\";
}
@Override
public void eat(String food) {
System.out.println(\"我喜欢吃\" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println(\"====================通用方法一====================\");
}
public void method2(){
System.out.println(\"====================通用方法二====================\");
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat(\"四川麻辣烫\");
System.out.println(\"*****************************\");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
me() + \",\");
}
}
System.out.println(\"{\\n\\t\");
System.out.println(\"}\");
}
}
3.构造器
/*
获取构造器结构
*/
@Test
public void test1(){
Class<Person> clazz = Person.class;
//getConstructors():获取当前运行时类声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor cons : constructors) {
System.out.println(cons);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor cons : declaredConstructors) {
System.out.println(cons);
}
}
4.运行时类的父类
/*
获取运行时类的父类
*/
@Test
public void test2(){
Class<Person> clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
5.运行时类的带泛型的父类
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
6.运行时类的带泛型的父类的泛型
/*
获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4(){
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
7.其他
/*
获取运行时类的带泛型的接口
*/
@Test
public void test5(){
Class<Person> clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println();
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c : interfaces1) {
System.out.println(c);
}
}
/*
获取运行时类所在的包
*/
@Test
public void test6(){
Class<Person> clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations){
System.out.println(a);
}
}
反射应用三:调用运行时类的指定结构
调用指定的属性:
/*
操作运行时类的指定属性
*/
@Test
public void tesField1() throws Exception{
Class<Person> clazz = Person.class;
//创建一个运行时类的实例
Person p = clazz.newInstance();
Field name = clazz.getDeclaredField(\"name\");
//保证当前属性是可以访问的
name.setAccessible(true);
/*
修改运行时类的属性的值
set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
*/
name.set(p,\"BEEA\");
/*
获取当前属性内容
get():参数1:获取哪个对象的当前属性值
*/
String pName = (String) name.get(p);
System.out.println(pName);
}
调用指定的方法:
/*
操作运行时类的指定方法
*/
@Test
public void testMethod() throws Exception {
Class<Person> clazz = Person.class;
//创建一个运行时类的实例
Person p = clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod(\"show\", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3.调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object chn = show.invoke(p, \"CHN\");
System.out.println(chn);
System.out.println(\"=====调用静态方法=====\");
Method showDesc = clazz.getDeclaredMethod(\"showDesc\");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object invoke = showDesc.invoke(Person.class);
System.out.println(invoke);
}
调用指定的构造器:
/*
操作运行时类的指定构造器
*/
@Test
public void testConstructor() throws Exception {
Class<Person> clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor<Person> cons = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
cons.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person person = cons.newInstance(\"Tom\");
System.out.println(person);
}
反射应用四:动态代理
1.代理模式的理解:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
2.静态代理:
//代理类和被代理类要实现的接口
public interface ClothFactory {
void produceCloth();
}
//代理类
public class ClothProxyFactory implements ClothFactory{
private ClothFactory factory;
public ClothProxyFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println(\"生产衣服的准备工作!\");
factory.produceCloth();
System.out.println(\"生产衣服的收尾工作!\");
}
}
//被代理类
public class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println(\"NIKE生产衣服!\");
}
}
//测试类
public class StaticProxyTest {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ClothProxyFactory clothProxyFactory = new ClothProxyFactory(nike);
clothProxyFactory.produceCloth();
}
}
静态代理的缺点:
代理类和目标 对象的类都是在编译期间确定下来,不利于程序的扩展。
每一个代 理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
3.动态代理:
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序
运行时根据需要动态创建
目标类的代理对象。
4.动态代理的实现:
4.1 需要解决的两个问题
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
(通过InvocationHandler接口的实现及其方法invoke())
4.2 代码
package com.atguigu.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 动态代理的举例
* @author hanhan
* @date 2022/8/11 20:05
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return \"I believe I can fly!\";
}
@Override
public void eat(String food) {
System.out.println(\"我喜欢吃\" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println(\"====================通用方法一====================\");
}
public void method2(){
System.out.println(\"====================通用方法二====================\");
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat(\"四川麻辣烫\");
System.out.println(\"*****************************\");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
体会:反射的动态性。
码云
来源:https://www.cnblogs.com/hanhanz/p/16609349.html
本站部分图文来源于网络,如有侵权请联系删除。