从零开始的Java学习生活
一、Java基础语法
1.1 注释
注释是在程序指定位置添加的说明性信息,即对代码的一种解释
注释分类:
- 单行注释
// System.out.println();
- 多行注释
/*
System.out.println();
*/
- 文档注释
/**
System.out.println();
*/
注释内容不会参与编译和运行
1.2 关键字
被Java赋予了特定含义的英文单词,不可用于常量、变量和任何标识符的名称
1.3 字面量
数据在程序中的书写格式
- 字符串 “Hello” 必须加双引号
- 字符 ‘A’ 必须加单引号,且只能有一个字符
- 有的特殊字符无法直接表示,需要通过转义字符表示
- 例如:换行符(enter) 制表符(Tab)
- 换行符(enter)‘\n’
- 制表符 (Tab) ‘\t’
- 转义字符
\
和其后面的第一个字母构成特定的含义
- 布尔值 true false
1.4 变量
内存中的存储空间,控件中存储着经常发生变化的数据
-
定义:数据类型 变量名 = 数据值;
-
int age = 22; int id;
tips:
- 变量名不允许重复定义
- 一条语句可以定义多个变量
- 变量在使用之前一定要进行赋值
- 要注意变量的作用域范围
1.5 标识符
给类、方法、变量等起名字的符号
命名规则:
- 由数字、字母、下划线(_)、**美元符($)**组成
- 不能以数字开头
- 不能是关键字
- 区分大小写
命名规范:
- 小驼峰命名法:变量
- 标识符为一个单词时,所有字母小写
- name
- 标识符为多个单词时,第一个单词首字母小写,其他大写
- firstName
- 大驼峰命名法:类
- 标识符为一个单词时,首字母大写
- Student
- 标识符为多个单词时,每个单词的首字母都大写
- GoodStudent
1.6 数据类型
基本数据类型
数据类型 | 关键字 | 取值范围 | 内存占用 | 默认值 |
---|---|---|---|---|
整数 | byte | -128~127 | 1 | 0 |
short | -32768~32767 | 2 | 0 | |
int (默认) | -231~231-1 | 4 | 0 | |
long | -263~263-1 | 8 | 0L | |
浮点数 | float | 单精度 32位 | 4 | 0.0f |
double(默认) | 双精度 64位 | 8 | 0.0d | |
字符 | char | 0~65535 | 2 | |
布尔 | boolean | true,false | 1 | false |
引用数据类型
- 类
- 数组
- 接口
1.7 Scanner 键盘输入
Scanner sc = new Scanner(System.in);
int age = sc.nextInt();
String name = sc.next();
/*
nextLine()方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的。
next()会自动消去有效字符前的空格,只返回输入的字符,不能得到带空格的字符串。
*/
二、运算符
2.1 算数运算符
运算符:对字面量或者变量进行操作的符号
表达式:用运算符把字面量或者变量连接起来符合Java语法的狮子就可以称为表达式
int a = 12;
int b = 20;
int c = a + b;
+ - * / %(取余)
Tips:
- 除法
- 如果两侧是整数,结果是整数
- 如果有一侧是浮点数,结果是浮点数
- 对精度要求比较高的场合不要使用基本数据类型进行运算
2.2 自增自减运算符
++ 自增
– 自减
使用方式:
-
单独使用
- ++ – 无论是放在变量前还是变量后,结果一样
-
参与操作使用
-
如果放在变量的后面,先拿变量的值进行运算,再对变量的值进行+1、-1 ,先运算再加减、
int a = 10;
int b = a++;
— b = 10; a = 11;
-
如果放在变量的前面,先加减再进行运算
int a = 10;
int b = ++a;
— b = 11; a = 11;
-
++、-- 只能操作变量,不能操作常量
-
2.3 类型转换
- 隐式转换
- 把一个取值范围小的数值或者变量,赋值给另一个取值范围大的变量
- 取值范围小的数据,和取值范围大的数据进行运算,小的会先提升称为大的之后,再进行运算
- byte short char 三种数据在算术运算的时候,都会提升为int,再进行运算
- 当把任何基本类型的值和字符串值进行连接运算时(+),基本类型的值将自动转化为字符串类型(基本数据类型转换成字符串)。
byte a = 10;
byte b = 20;
byte c = (byte)(a + b);
- 强制转换
- 把一个取值范围很大的数值或者变量,赋值给另一个取值范围小的变量,不允许直接赋值,需要进行强制转换
- 格式:目标数据类型 变量名 = ( 目标数据类型 ) 被强制转换的数据;
double a = 8.88;
int b = (int) a;
//输出: 8
2.4 赋值运算符
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | a = 10; 将10赋值给变量a |
+= | 加后赋值 | a += b; 将 a + b 的和赋给 a |
-= | 减后赋值 | a -= b; 将 a - b 的差赋给 a |
*= | 乘后赋值 | a *= b; 将 a * b 的积赋给 a |
/= | 除后赋值 | a /= b; 将 a / b 的商赋给 a |
%= | 取余后赋值 | a %= b; 将 a % b 的余数赋给 a |
2.5 关系运算符
符号 | 说明 |
---|---|
== | a == b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a != b,判断a和b的值是否不相等,成立为true,不成立为false |
> | a > b,判断a是否大于b,成立为true,不成立为false |
>= | a >= b,判断a是否大于等于b,成立为true,不成立为false |
< | a < b,判断a是否小于b,成立为true,不成立为false |
<= | a <= b,判断a是否小于等于b,成立为true,不成立为false |
关系运算符的结果都是 boolean 类型,要么是true要么是false
2.6 逻辑运算符
连接布尔类型的表达式,或者是值
整合多个条件,为一段整体的逻辑
符号 | 介绍 | 说明 |
---|---|---|
& | 逻辑与 | 并且,有false则false |
| | 逻辑或 | 或者,有true则true |
! | 逻辑非 | 取反 |
^ | 逻辑异或 | 二者相同为true,不同为false |
&& | 短路与 | 作用与 & 相同,但是有短路效果 |
|| | 短路或 | 作用与 | 相同,但是有短路效果 |
-
逻辑与 & ,无论左边是什么, 右边都要执行
短路与&&,只要左边为false,总体为false,不再执行右边
-
逻辑或 |,无论左边是什么,右边都要执行
短路或 ||,只要左边为true,总体为true,不再执行右边
2.7 三元运算符
格式:判断条件 ?
表达式1 : 表达式2;
执行流程:
- 计算判断条件
- 若为 true,结果返回表达式1的值
- 若为false,结果返回表达式2的值
总结:根据判断条件,从两份数据中二者选其一,正确选左边,错误选右边
2.8 运算符优先级
三、方法
3.1 方法介绍
方法:一段具有特定功能的代码块
- 按照功能进行分类
- 提高代码复用性
相同功能的代码进行抽取,在需要的场合去使用 – 方法/函数
3.2 定义及调用
定义格式:
修饰符] void 方法名() {
//方法体
}
[修饰符] 返回值类型 方法名(参数类型 参数名1, 参数类型 参数名2) {
//方法体
return 返回值;
}
/*
1.修饰符可以省略,目前统一写 public static
2.返回值类型本质是数据类型(基本数据类型、引用数据类型)
3.方法名本质是起名字,要符合标识符的命名规范
4.参数列表 - 格式:
数据类型1 参数名1, 数据类型2 参数名2, 数据类型3 参数名3
*方法接收几个参数,就要定义几个参数
*最后一个参数的后面没有" , "
*如果方法没有输入,只写小括号,小括号里没有东西
*数据类型用来限制输入的参数类型
*参数名 - 要符合标志符的命名规范
5.{}中的内容是方法体,方法完成的功能都要写在方法体中
6.*“return 返回值;” 表示方法返回给外界的内容
*“返回值” 要和 “返回值类型”兼容
*方法可以没有返回值,此时“返回值类型要写成void”
*不写return语句
*return;
7.方法要定义在类中,目前要和main平级
首先要确定返回值类型、方法名、参数列表
*/
调
、 用格式
方法名();
方法名(参数1, 参数2);
- 形参:形式参数,定义方法时,所声明的参数
- 实参:实际参数,调用方法时,所传入的参数
Tips:
- 方法不调用就不执行
- 方法与方法之间是平级关系,不能嵌套定义
- 方法的编写顺序和执行顺序无关
- 方法的返回值类型为void,表示该方法没有返回值,没有返回值的方法可以省略return语句不写如果要编写return,后面不能跟具体的数据
- return语句下面,不能编写代码,因为永远执行不到,属于无效的代码
3.3 方法重载
在同一个类中,定义了多个同名的方法,但每个方法具有不同的参数类型或参数个数
Tips:
- 识别方法之间是否是重载关系,只看方法名和参数,跟返回值无关
- 在调用时,JVM通过参数列表的不同来区分同名方法
public class MethodDemo {
public static void fn(int a, double b) {
//方法体
}
public static void fn(double a, int b) {
//方法体
}
}
/*
1.相同 - 同一个类中,方法名相同
2.不同 - 参数列表不同
1)个数不同
2)参数类型不同
3)顺序不同
4)和参数的名字没有关系
3.方法重载和返回值类型没有关系
*/
public class MethodDemo {
public static int fn(int a, int b) {
return a + b;
}
public static int fn(int a, int b, int c) {
return fn(a, b) + c;
}
}
/*
1.方法可以根据需要调用多次
2.自定义的方法可以调用其他方法(自定义方法、官方提供的方法)
3.调用方法之后,运行的流程就从目前的流程脱离,进入被调用的方法,被调用的方法运行结束之后,会将结果返回给调用者,调用者继续执行后续流程
*/
方法递归 – 方法调用方法自身
合法的递归调用
1. 方法调用方法自身
1. 要有明确的退出条件
1. 如果没有明确的推出条件会出现栈溢出,
四、控制流程
4.1 流程控制语句
通过一些语句,来控制程序的执行流程
- 顺序结构
- 分支结构
- 循环结构
4.2 顺序结构
Java程序默认的执行流程,没有特定的语法结构,按照代码的先后顺序执行
public class Test {
public static void main(String[] args) {
System.out.println("A");
System.out.println("B");
System.out.println("C");
}
}
4.3 分支结构
代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个或多个,所以产生选择,按照不同的选择执行不同的代码
4.3.1 if 语句
- if
格式:
if (判断条件) {
语句体;
}
执行流程:
-
首先计算判断条件的结果
-
如果条件的结果为true就执行语句体
-
如果条件的结果为false就不执行语句体
-
if… else…
格式:
if (判断条件) {
语句体1;
} else {
语句体2;
}
执行流程:
-
首先计算判断条件的结果
-
如果条件的结果为true就执行语句体1
-
如果条件的结果为false就执行语句体2
-
if… else if… else if… else
格式:
if (判断条件1) {
语句体1;
} else if (判断条件2) {
语句体2;
}
...
else {
语句体n;
}
执行流程:
- 首先计算判断条件1的值
- 如果值为true就执行语句体1;如果值为false就计算判断条件2的值
- 如果值为true就执行语句体2;如果值为false就计算判断条件3的值
- …
- 如果没有任何判断条件为true,就执行语句体n。
Tips:
- if语句中,如果大括号控制的是一条语句,大括号可以省略不写
- if语句的( )和{ }之间不要写分号
4.3.2 switch 语句
格式:
switch (表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
…
default:
语句体n+1;
}
格式说明:
- 表达式:(将要匹配的值)取值为byte、short、int、char 在JDK5以后可以是枚举,JDK7以后可以是String
- case:后面跟的是要和表达式进行比较的值(被匹配的值)
- break:表示中断,结束的意思,用来结束switch语句。
- default:表示所有情况都不匹配的时候,就执行该处的内容,和if语句的else相似
执行流程:
- 拿着表达式的值,依次和case后面的值进行匹配,匹配成功,就会执行对应的语句,在执行的过程中,遇到break就会结束
- 如果所有的case都不匹配,就会执行default里面的语句体,然后程序结束掉
switch穿透:
现象:当开始case穿透,后续的case就不会具有匹配效果,内部的语句都会执行直到看见break,或者将整体switch语句执行完毕,才会结束
原因:由于某个case语句中缺少或者漏写break语句所导致的结果
int input = sc.nextInt();
switch (input) {
case 1:
System.out.println("输入为:" + 1);
//break;
case 2:
System.out.println("输入为:" + 2);
case 3:
System.out.println("输入为:" + 3);
case 4:
System.out.println("输入为:" + 4);
break;
default:
System.out.println("输入错误");
}
//当input为1时,输出结果为1 2 3 4 ,运行直至遇到case 4中的break;才停止
Tips:
- case给出的值不允许重复
- case后面的值只能是常量,不能是变量
- default后面可以不加break;
4.4 循环语句
在某些条件满足的情况下,反复运行某段代码
4.4.1 for 语句
格式:
for(初始化语句; 条件语句; 控制语句) {
//循环体语句
}
执行流程:
-
执行初始化语句
-
执行条件语句,看其结果是true还是false
- 如果是
false
,循环结束 - 如果是
true
,继续执行
- 如果是
-
执行循环体语句
-
执行控制语句
-
回到2继续
流程图:
Tips:
- 循环{ }中定义的变量,在每一轮循环结束后,都会从内存中释放
- 循环( )中定义的变量,在整个循环结束后,都会从内存中释放
- 循环语句( )和{ }之间不要写分号
循环嵌套
在循环语句中,继续出现循环语句
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(i + "*" + j + "=" + i * j + "\t");
}
System.out.println();
}
4.4.2 while 语句
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
4.4.3 do… while 语句
初始化语句;
do {
循环体语句;
条件控制语句;
} while (条件判断语句);
Tips:无论条件是否满足,至少执行一次
4.5 三种循环的区别
区别:
- for 循环 和 while 循环 都是先判断再执行
- do… while 循环是先执行再判断
- for 循环中,控制循环的变量,在循环结束之后不能被再次访问
- while 循环中,控制循环的变量,在循环结束之后可以被再次访问
4.6 跳转控制语句
- break:终止循环体内容的执行,也就是说结束当前的整个循环
- continue:跳过某次循环体内容的执行,继续下一次的执行
Tips:
- break:只能在循环和switch当中进行使用
- continue:只能在循环中进行使用
4.7 Random 随机数
方法:
Random r = new Random();
int a = r.nextInt(100);
System.out.println(a);
Tips:
r.nextInt(100);中的 100 ,指的是产生0~99的随机数,取不到100
五、数组
5.1 数组
数组是一种容器,是存储同一种数据类型的多个元素的集合
- 数组既可以存储基本数据类型,也可以存储引用数据类型
- 数组属于引用数据类型,可以理解为对象(Object),数组中的每个元素相当于该对象的成员变量
- 数组一旦初始化,长度不可变 数组长度:
数组名.length
5.1.1 数组静态初始化
初始化:就是在内存中,为数组容器开辟空间,并将数据存入容器中的过程
数组定义格式:
- 格式一:
- int[] array;
- 格式二:
- int array[];
int[] array1;
int array2[];
完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3};
int[] array1 = new int[]{11, 22, 33};
double array2[] = new double[]{1.1, 2.2, 3.3};
简化格式:
数据类型[] 数组名 = {元素1, 元素2, 元素3};
int[] array3 = {11, 22, 33};
5.1.2 数组元素访问
格式:数组名[索引];
索引:数组容器中空间的编号,编号从0开始,逐个+1增长
int[] array3 = {11, 22, 33};
System.out.println(array3[0]);
//输出值为 11
5.1.3 数组遍历访问
数组遍历:将数组中所有的内容取出来,取出来之后进行操作
int[] array3 = {11, 22, 33};
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]);
}
增强for循环(foreach)
/*
增强for循环
for(数据类型 变量 : 数组) {
循环体;
}
每次循环,会将数组中的元素依次赋值给变量
*/
for (int item : arr)
System.out.println(item);
Tips:
- 只能从头到尾的遍历数组或集合,而不能只遍历部分
- 在遍历List或数组时,不能获取当前元素下标
5.1.4 数组动态初始化
动态初始化:初始化时只指定数组的长度,由系统为数组分配初始值
- 格式:
数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[5];
初始值:
-
基本数据类型
-
byte : 0
-
short : 0
-
int : 0
-
long : 0
-
float : 0.0
-
double : 0.0
-
char : `\u0000`
-
boolean : false
-
-
存放引用数据类型(类、接口、数组)的数组,每个元素的初始值为
null
5.1.5 两种初始化的区别
- 静态初始化:手动指定数组元素,系统根据数组元素个数,计算数组长度
- 动态初始化:手动指定数组长度,由系统给出默认初始化值
5.1.6 数组常见异常
索引越界异常
- ArrayIndexOutOfBoundsException
- 访问了数组中不存在的索引
空指针异常
- NullPointerException
- 当引用数据类型变量被赋值为null之后,地址的指向被切断,还继续访问堆内存数据,就会引发空指针异常
int[] arr1 = new int[]{1, 2, 5, 0};
System.out.println(arr1[4]);
int[] arr2 = null;
System.out.println(arr2[0]);
5.1.7 可变长参数
在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用
本质就是数组
- 语法:数据类型… 参数名
在定义方法时,在最后一个形参后加上三点 …
,就表示该形参可以接受多个参数值,多个参数值被当成数组传入
public class Varargs {
public static void test(String... args) {
for(String arg : args) {
System.out.println(arg);
}
}
public static void main(String[] args) {
test();//0个参数
test("a");//1个参数
test("a","b");//多个参数
test(new String[] {"a", "b", "c"});//直接传递数组
}
}
Tips:
- 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
- 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
- Java的可变参数,会被编译器转型为一个数组
- 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立
- 方法重载时,优先匹配固定参数,调用一个被重载的方法时,如果此调用既能够和固定参数的重载方法匹配,也能够与可变长参数的重载方法匹配,则选择固定参数的方法
- 调用一个被重载的方法时,如果此调用既能够和两个可变长参数的重载方法匹配,则编译出错
5.2 二维数组
一种容器,用于存储一维数组
5.2.1 二维数组静态初始化
格式:
数据类型[][] 数组名 = new 数据类型[][]{{元素1, 元素2}, {元素3, 元素4}, {元素5, 元素6}};
int[][] arr = new int[][]{{11, 22}, {33, 44}};
简化格式:
数据类型[][] 数组名 = {{元素1, 元素2}, {元素1, 元素2}};
int[][] arr1 = {{11, 22}, {33, 44}};
5.2.2 二维数组元素访问
格式: 数组名 [索引][索引];
int[][] arr = new int[][]{{11, 22}, {33, 44}};
arr[1][0];
5.2.3 二维数组元素遍历
思路:
- 遍历二维数组,取出里面每一个一维数组
- 在遍历的过程中,对每一个一维数组继续完成遍历,获取内部存储的每一个元素
int[][] arr = {{11, 12, 13, 14, 15}, {21, 22, 23, 24}, {31, 32, 33}};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
//foteach
for (int[] i : arr) {
for (int j : i) {
System.out.print(j + "\t");
}
System.out.println();
}
5.2.4 二维数组动态初始化
格式: 数据类型[][] 数组名 = new 数据类型[m][n];
m表示这个二维数组可以存放多少个一维数组,若把二维数组看作一个矩阵,则为矩阵的行数
n表示这个二维数组的某行可以放多少个元素,即为矩阵的列数
int[][] arr = new int[2][3];
//二维数组arr有2个一维数组,每个一维数组可以放3个元素
Tips:
可以在二维数组中存放创建好的一维数组
int[][] arr = new int[2][3];
int[] arr1 = {11, 22, 33};
int[] arr2 = {44, 55, 66};
arr[0] = arr1;
arr[1] = arr2;
六、面向对象
6.1 类和对象
面向对象编程(Object Oriented Programming)
- 类是对一类事物描述,是抽象的、概念上的定义,类指的是一组相关属性和行为的集合,可以将其看作为时一张对象的设计图
- 对象是实际存在的该类事务的每个个体,因而也称实例(instance)
- 一切客观存在的事物,万物皆对象
- 任何对象,一定有自己的特征和行为
类和对象的关系:
- 依赖关系:
- Java中要想创建对象,必须要先有类的存在,Java需要根据类创建对象
- 数量关系:
- 一个类,可以创建出多个对象
类的组成:
- 类的本质:对事务进行的描述
- 属性:在代码中使用成员变量表示,成员变量跟之前定义变量的格式一样,只不过位置发生了变化,类中方法外
- 方法:在代码中使用成员方法表示,成员方法跟之前定义方法的格式一样,只不过要去掉 static 关键字
public class Student {
public String name ;
public int age;
public void study() {
System.out.println("学习");
}
public void eat() {
System.out.println("吃饭");
}
}
创建对象及对象使用格式
-
创建对象
类名 对象名 = new 类名();
-
变量使用格式
对象名.变量名;
-
方法使用格式
对象名.方法名(实际参数);
public class TestStudent {
public static void main(String[] args) {
//创建对象
Student stu = new Student();
//使用成员变量
stu.name = "王瑞宁";
stu.age = 20;
System.out.println(stu.name);
System.out.println(stu.age);
//使用成员方法
stu.study();
stu.eat();
}
}
6.2 成员变量和局部变量
区别 | 成员变量 | 局部变量 |
---|---|---|
定义位置 | 类的内部,方法外面 | 方法中 |
初始化值 | 有默认初始化值 | 没有,使用之前完成赋值 |
内存位置 | 堆内存 | 栈内存 |
生命周期 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
作用区域 | 在自己归属的大括号中 | 在自己归属的大括号中 |
6.3 构造方法
6.3.1 构造方法概述
-
构造器
初始化一个新建的对象
构建、创造对象的时候,所调用的方法
-
格式:
- 方法名与类名相同,大小也要一致
- 没有返回值类型,连void都没有
- 没有具体的返回值
-
执行时机:
- 创建对象的时候调用,每创建一次对象,就会执行一次构造方法
- 不能手动调用构造方法
6.3.2 构造方法作用
- 本质作用:创建对象
- 结合构造方法执行时机:给对象中的属性(成员变量)进行
初始化
6.3.3 构造方法注意事项
class Student {
int age;
//默认构造方法
public Student() {
}
//带参构造方法
public Student(int age) {
this.age = 18;
}
}
- 构造方法的创建
- 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
- 构造方法也是方法,允许重载
- 推荐的使用方式
- 无参数构造方法和带参构造方法一起给出
6.4 this 关键字
当局部变量和成员变量出现了重名的情况时,java使用的时就近原则
6.4.1 this 关键字介绍
this 代表当前类对象的引用(地址)
在java中**表示当前类的对象,可以理解成指向对象本身的一个指针。通俗地说就是表示当前类对象”自己“,它是在对象被创建时自动产生的。我们使用this,可以用来调用本类的属性、方法、构造方法。**当我们在构造方法中使用this时,this表示的是当前类的成员变量。
6.4.2 this 关键字作用
- 表示当前类对象
- 调用当前类中的属性(成员变量)
- 调用当前类中的方法或构造方法
public class Phone {
public String brand;
public String color;
public int price;
public Phone() {
this(null, null, 0);
}
public Phone(String brand, int price) {
this(brand, null, 0);
this.price = price;
}
public Phone(String brand, String color, int price) {
}
public void call() {
this.sendMessage();
System.out.println("使用" + color + brand + "打电话");
}
public void sendMessage() {
System.out.println("使用" + color + brand + "发消息");
}
}
七、面向对象三大特征
7.1封装
7.1.1 何为封装
使用类设计对象时,将需要处理的数据,以及处理这些数据的方法,涉及到对象中,尽可能隐藏对象内部的实现细节,控制对象的修改及访问的权限
封装的设计规范:
- 合理隐藏,合理暴露
作用:
- 更好地维护数据
- 使用者无需关心内部实现,只要知道如何使用即可
7.1.2 权限修饰符
同一个类中 | 同一个包中 | 不同包的子类 | 不同包的无关类 | |
---|---|---|---|---|
private | √ | × | × | × |
default | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
访问修饰控制符 – “权限”
属性/方法被不同的访问控制符修饰,外界的访问权限是不同的
三个修饰符 private protected public
四个级别 private default protected public default是默认的
alt + enter:创建子类
-
private(私有的)
- 可以修饰成员变量,成员方法,构造方法,不能修饰外部类,被private修饰的成员只能在其修饰的本类中访问,在其它类中不能被调用。对于所有的成员变量,可以通过setXXX和getXXX向外界提供访问方式
-
default(默认的)
- 不写任何关键字,可以修饰类,成员变量,成员方法,构造方法。只能被本类以及同包下的其他类访问
-
protected(受保护的)
- 可以修饰成员变量、成员方法、构造方法,但不能修饰类。只能被同包下的其他类访问,如果不同包下的类要访问被protected修饰的成员,这个类必须是子类
-
public(公共的)
- 权限最大的修饰符,可以在任意一个类中使用,不管同不同包
7.1.3 如何封装
将属性使用访问修饰符private
进行修饰,被private
修饰后,属性仅在本类可见。
提供公共的方法(被public
修饰)getXXX
和setXXX
实现对该属性的操作。
在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全(可选的)。
JavaBean:
package ithm;
/**
* @author wxb
* @version 1.0
* @intro: 练习使用类
*/
public class Student {
public String name ;
public int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
Tips:
- 不可以直接访问属性,仅可以访问公共方法
get/set
方法是外界访问对象私有属性的唯一通道,方法内部可以对数据进行检验和过滤
7.2 继承
7.2.1 何为继承
继承:让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员
程序中的继承,是类与类之间特征与行为的一种赠与或获得
两个类之间的继承关系,必须满足 is a 关系
- Dog is an Animal.
- Cat is an Animal.
根据上面的关系可以设计三个类:Dog、Cat、Animal
- Dog 继承 Animal
- Cat 继承 Animal
被继承的类为父类,即Animal
继承的类称为子类,即Dog和Cat
如何选择父类:
- 功能越细致,重合点越多,越接近直接父类
- 功能越粗略,重合点越少,越接近Object类
在实际开发中,可根据程序需要使用到的多个具体类,进行共性提取,进而定义父类
在一组相同或类似的类中,抽取出共性的特征和行为,定义在父类中,实现重用
作用:
提高了代码的复用性和可扩展性
1. 公共代码抽取
2. is a 的关系
3. 目前存在的问题 -- 代码冗余/重复,修改不方便
4. 如何解决? -- 抽取公共代码 -- 继承
5. Java程序中的继承是类与类之间的一种关系 --- 最少要有两个类
6. 两个类之间的继承关系,必须满足 is a 的关系
如果不满足is a 的关系,即便有公共的代码,也不能使用继承
学生 is a Person 使用
教师 is a Person 使用
Cat is not a Person 不能继承
7. 被继承的类,成为“父类” Person
继承的类,成为“子类” Student Teacher
8. 如何实现继承?
[修饰符] class 子类名 extends 父类名 {
属性;
构造方法;
方法;
}
1. 修饰符可以省略,目前统一写public
2. 子类名和父类名都是类名,要符合命名规范
3. 子类/多个子类公共的内容定义在父类中
4. 子类中定义子类自己特有的内容
5. 实现继承关系之后,“子类可以使用父类的属性和方法”
6. extends -- “扩展” -- 子类是对父类的扩展
父类不能满足要求,使用子类进行扩展
9. 关于单继承
1. 什么是单继承?一个类只能有一个”直接父类“
extends后面只能写一个父类
2. 一个类可以有多个”间接父类“
Object类是所有类的”父类“
定义类时如果指定了父类,Object就是间接父类
定义类时如果没有指定父类,Object就是直接父类
===> 任何的类都可以使用Object类的属性和方法
7.2.2 如何继承
//继承语法
class 子类名 extends 父类名 { //定义子类时,指定其父类
//属性(成员变量)
//构造方法
//成员方法
}
产生继承关系后,子类可以使用父类中的属性和方法,也可以定义子类独有的属性和方法
继承特点:Java为单继承,一个类只能有一个直接父类,但是可以多级继承,属性和方法逐级叠加
//父类
class Employee {
String name;
int age;
double salary;
}
//子类继承父类
class Coder extends Employee{
}
关于不可继承:
- 类中的构造方法,只负责创建本类对象,不可继承
private
修饰的属性和方法,仅本类可见- 父子类不在同一个
package
中时,default
修饰的属性和方法
7.2.3 方法重写
当父类提供的方法无法满足子类需求时,可以在子类中定义和父类相同的方法进行重写(Override)
方法重写原则:
- 子类重写父类方法,方法声明完全一致,方法名称、参数列表、返回值类型必须与父类相同
- 访问修饰符可与父类相同或是比父类更宽泛,访问权限必须大于等于父类
- 父类中的私有方法不可被重写
方法重写的执行:
- 根据就近原则,子类重写父类方法后,调用时优先执行子类重写后的方法
@Override – 注解
- 书写位置:写在重写的方法上面
- 作用:告诉编译器,当前的方法是重写的方法,让编译器协助检查是否符合重写的规范,如果不符合就报错
- 建议:只要重写方法就要在重写的方法上添加这个注解
重写方法的调用
- 如果通过子类对象调用重写方法,会优先调用子类自己重写的方法,如果子类没有重写方法,就调用父类的方法
- 如果通过父类调用方法(这个方法被子类重写过),此时调用的是父类的方法
alt + enter:创建重写
public class Dog extends Animal {
private String color;//毛色
//跑
public void run() {
System.out.println("run...");
}
//子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
public class MyTest1 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); //调用子类中重写的方法。
}
}
7.2.4 super 关键字
子类继承父类后,可以直接访问从父类中继承到的属性和方法,但当父类和子类中的属性或方法存在重名的情况(属性遮蔽、方法重写)时,需要加以区分,通过this.
调用本类方法和属性,使用super.
调用父类中的方法和属性
public class Dog extends Animal {
private String color;//毛色
//跑
public void run() {
System.out.println("run...");
}
//子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
@Override
public void eat() {
super.eat();
System.out.println("狗吃骨头...");
}
}
super调用父类成员的省略规则:
- super.父类成员变量;
- super.父类成员方法();
- 被调用的成员变量或者成员方法在子类中不存在时,可以省略
super.
- 被调用的成员变量或者成员方法在子类中不存在时,可以省略
super
关键字可以在子类中访问父类的方法,使用super.
的形式访问父类的方法,进而完成在子类中的复用,叠加额外的功能代码,组成新的功能
由于父类与子类的同名属性不存在重写关系,所以两块空间同时存在,但在使用中,由于就近原则,子类中的属性会遮蔽父类属性,所以需要使用不同的前缀进行访问
this 和 super
- this:代表本类对象的引用
- super:代表父类存储空间的标识
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.本类成员变量; | this.本类成员方法; | this(); this(…);本类构造方法 |
super | super.父类成员变量; | super.父类成员方法; | super(); super(…);父类构造方法 |
Tips:
父子类的同名属性不存在重写关系,两块空间同时存在(子类遮蔽父类属性),需使用不同前缀进行访问
public class A {
int value = 10;
}
public class B extends A {
int value = 20; //子类属性遮蔽父类属性
public void print() {
int value = 30;
System.out.println(value); //访问局部变量,输出B成员方法中的value 为30
System.out.println(this.value); //访问本类的属性,输出B中的成员变量 value 为20
System.out.println(super.value); //访问父类的属性,输出父类中的成员变量 value为 10
}
}
public class MyTest2 {
public static void main(String[] args) {
B b = new B();
b.print();//分别输出30、20、10
}
}
继承中的对象创建:
先初始化父类属性,再调用构造方法
在具有继承关系的对象创建中,构建子类对象会先构建父类对象,由父类的共性内容,叠加子类的独有内容,组成完整的子类对象
public class X {
public X() {
System.out.println("X的构造方法...");
}
}
public class Y {
public Y() {
System.out.println("Y的构造方法...");
}
}
public class A {
private X x = new X();
public A() {
System.out.println("A的构造方法...");
}
}
public class B extends A {
private Y y = new Y();
public B() {
System.out.println("B的构造方法...");
}
}
public class C extends B {
public C() {
System.out.println("C的构造方法...");
}
}
public class MyTest3 {
public static void main(String[] args) {
C c = new C();
}
}
/* 输出:
X的构造方法...
A的构造方法...
Y的构造方法...
B的构造方法...
C的构造方法...
*/
构建过程:
- A类
- 默认构建父类对象
Object
; - 初始化A的属性;
- 执行A的构造方法代码。
- 默认构建父类对象
- B类
- 构建父类对象A;
- 初始化B的属性;
- 执行B的构造方法代码。
- C类
- 构建父类对象B;
- 初始化C的属性;
- 执行C的构造方法代码。
代表父类构造方法
super();
:表示调用父类无参构造方法,若没有显示书写,隐式存在于子类构造方法的首行
super(参数);
:表示调用父类有参构造方法
public class A {
private X x = new X();
public A() {
System.out.println("A的构造方法...");
}
public A(int value) {
System.out.println("A的构造方法..." + value);
}
}
public class B extends A {
private Y y = new Y();
public B() {
//super(); //默认存在
System.out.println("B的构造方法...");
}
public B(int value) {
super(value);
System.out.println("B的构造方法..." + value);
}
}
public class MyTest4 {
public static void main(String[] args) {
B b = new B(100);
}
}
/* 输出:
X的构造方法...
A的构造方法...100
Y的构造方法...
B的构造方法...100
*/
this
或super
使用在构造方法中时,都要求在首行
当子类构造中使用了this()
或this(参数)
,就不可再同时书写super()
或super(参数)
,会由this()
指向的构造方法完成super()
的调用
this()或this (参数)会默认调用父类的构造方法,如果再调用super()或者super (参数)就会创建多个父类对象
public class A {
private X x = new X();
public A() {
System.out.println("A的无参构造...");
}
public A(int value) {
System.out.println("A的有参构造..." + value);
}
}
public class B extends A {
private Y y = new Y();
public B() {
super();
System.out.println("B的无参方法...");
}
public B(int value) {
this();
System.out.println("B的有参方法..." + value);
}
}
public class MyTest5 {
public static void main(String[] args) {
B b = new B(100);
}
}
/* 输出:
X的构造方法...
A的无参构造...
Y的构造方法...
B的无参方法...
B的有参方法...100
*/
super
总结:
两种用法:
- 在子类方法中使用
super.
的形式访问父类的属性和方法 - 在子类的构造方法的首行,使用
super()
或super(参数)
,调用父类构造方法
注意:
- 如果子类构造方法中,没有显式定义
super()
或super(参数)
,则默认提供super()
- 同一个子类构造方法中,
super()
、this()
不可同时存在
7.3 多态
7.3.1 何为多态
多种形态,同一个行为具有多个不同表现形式或形态的能力,父类引用指向子类对象,从而产生多种形态
多态前提:
- 有继承/实现关系
- 有方法重写
- 有父类引用指向子类对象
面对对象三大特征之一
多态 - 多种状态 – 编译时和运行时呈现出不同的状态就是多态
-
多态的代码如何编写 –
子类对象赋值给父类引用/
父类引用指向子类对象
Animal a1 = new Dog();
Animal a2 = new Cat(); -
方法调用规律
1)编译时,“看左边”,左边有哪个方法就可以调用哪个方法
2)运行时,“看右边”。右边实际是哪个方法,运行时就调用哪个方法 -
多态的应用
之前存在的问题 – 针对不同的类型要写特定的方法单独进行处理,产生代码冗余
– 设置一个方法可以兼容多种类型
1) 父类作为方法的参数,实现多态 – 方法的参数可以兼容所有的子类对象
2) 子类作为方法的返回值类型,实现多态
7.3.2 向下转型
-
(类型)引用
-
可能存在的问题 java.lang.ClassCastException 类型转换异常
-
如何解决2的问题,转换之前先判断引用代表的对象本质上是什么类型 – 如何判断?
引用 instanceof 类型
返回值
true 引用是后面的类型
false 引用不上后面的类型
八、常用类
8.1 API
8.1.1 什么是API
定义:
Application Programming Interface
,API
是应用程序编程接口,指一些预先定义好的类。
要求:
- 会用
- 现用现查
JavaSE8官方API JavaAPI
中文官方API 中文JavaAPI
8.1.2 Java常用类库简介
Java程序设计就是定义类的过程,定义的类分为两大类:
- 系统定义的类:即Java类库中的类。这部分类是已经设计好的,直接用就可以。也是我们这里所指的Java类库中的主要内容;
- 用户程序自定义的类:需要开发人员自己设计实现。
重要的包及其类:
java.lang
:包括了Java语言程序设计的基础类;java.util
:包含集合、日期和各种实用工具类;java.io
:包含可提供数据输入、输出相关功能的类;java.net
:提供用于实现Java网络编程的相关功能类;java.sql
:提供数据库操作相关功能类。
注意:java.lang
是默认会导入的包,不需要手动导入。
8.2 Object 类
8.2.1 何为Object类
- 是所有类的直接父类或间接父类
- 设计一个类时,如果没有指定父类,那么它的父类是Object
- 所有类都具有Object类的属性和方法
- Object类可以存储任何对象
作用:
- 作为参数,可以接收任何对象
- 作为返回值,可以返回任何对象
8.2.2 常用方法
-
toString方法 - 自身没有打印功能
-
定义:public String toString()
-
作用:返回对象的字符串表示形式
-
返回内容:默认返回当前对象在堆内存中的地址信息:全类名@hash值
-
注意:
- Object类是所有类的父类,其他的类都具有toString方法
- 使用System.out.println打印引用,会自动调用toString方法
- System.out.println(s.toString());
- System.out.println(s);
- 多数时候继承自Object类的toString方法无法满足要求,需要在子类中重写toString方法
- 重写toString方法通常是为了能够打印对象属性的值,使用Idea生成
-
equals方法
-
==的作用
-
判断基本数据类型的值是否相等
-
int a = 10; int b = 5; System.out.println(a == b); //false
-
-
判断两个对象是否是同一个对象/判断两个对象的地址是否相同/判断俩个引用存储的地址是否相同
-
Student s2 = new Student(); System.out.println(s1 == s2); //false s1 = s2; System.out.println(s1 == s2); //true
-
-
-
Object类中的equals方法
-
定义:public static equals(Object o)
-
返回内容:默认比较当前对象与另一个对象的地址是否相同
-
注意:
-
* Object类中的equals的作用和==的作用相同,都用来判断两个对象是否是同一个对象
* Object类是所有类的父类,其他的类都具有equals方法,如果子类中没有重写equals方法
那么子类对象调用equals实际调用的是Object类的equals方法
* 在多数情况下,我们希望equals方法能够判断对象的“值”(属性)是否相同
Object类的equals方法无法满足要求,因此需要在子类中重写equals方法
* ```java
Student s5 = new Student("Java01", "zs", 20);
Student s6 = new Student("Java01", "zs", 20);
System.out.println(s5.equals(s6)); //true
```
* 判断String的内容是否相同使用equals,为什么?
String类重写了Object类中的equals方法
//被重写后的equals方法和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(num, student.num) && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(num, name, age);
}
-
hashCode方法
-
定义:public int hashCode() {}
-
返回内容:对象的十进制哈希值,哈希值是通过哈希算法根据对象的地址计算出来的int类型的数组
-
注意:
- Object类的hashCode()方法为不同的对象返回不同的值,Object类的hashCode值,可以认为其表示对象的地址
- 如果equals()判断两个对象相等,那么他们的hashCode()方法应该返回同样的值
-
== 和 equals的区别
- == 判断的是基本数据类型的值是否相等或者是两个对象的地址是否相同
- equals在Object类中,判断的是两个对象的地址是否相同,重写后判断的是两个对象存储的内容是否相同
-
isNull方法
-
定义:public static boolean isNull(Object obj)
-
返回内容:判断变量是否为null
8.3 包装类
8.3.1 问题引入
目前,8种基本数据类型无法作为引用数据类型使用
8.3.2 如何解决
包装类
- 用一种方式将8种基本数据类型当成引用类使用
- 将基本数据类型,包装成类
8.3.3 包装类
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
8.3.4 装箱和拆箱 - Integer类
-
装箱:基本数据类型转换成包装类
-
拆箱:包装类转换成基本数据类型
-
手动拆装箱
-
//手动装箱 //基本数据类型 -- 包装类 int a = 100; Integer i = Integer.valueOf(a); System.out.println(i); //100 System.out.println(i.toString()); //100 //手动拆箱 //包装类 -- 基本数据类型 int b = i.intValue(); System.out.println(b);
-
-
自动拆装箱 - 直接赋值
-
//自动装箱 int a = 100; Integer i1 = a; System.out.println(i1); //100 //自动拆箱 int c = i1; System.out.println(c); //100
-
-
8.3.5 基本数据类型和字符串转换
- 基本数据类型 --> 字符串
//1. 基本数据类型 + "";
int a = 100;
String s1 = a + ""; //100
//2. String.valueOf(基本数据类型);
String s2 = String.valueOf(a); //100
- 字符串 --> 基本数据类型
int b = Integer.parseInt("100");
int b = Integer.valueof("100");
System.out.println(b); //100
8.3.6 常量池
Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2); //true
System.out.println(i1.equals(i2)); //true
Integer i3 = new Integer(128);
Integer i4 = new Integer(128);
System.out.println(i3 == i4); //false
System.out.println(i3.equals(i4)); //true
在上面的代码中,当我们新建两个值为127的Integer类时,通过==比较地址和通过重写equals比较内容都是相同的
但是在第二段代码中国,我们设置的值为128时,他们的地址就不再一样了
为什么呢?
-
池化技术
- 内存池
- 线程池
- 连接池
-
包装类的常量池 - Integer
-
public static Integer valueOf(int i) { if (i = IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
-
在类加载时,创建-128 ~ 127范围数字对应的Integer类的对象,它们都存放在常量池中,如果后面需要使用该范围内的数字,就从常量池中获取;如果没有,就需要重新创建。
- 所以,在最开始的代码中,127时,是从常量池中取到的两个地址相同的数字,128时,是两个新建的对象
-
-
如何判断包装类的对象是是否相等?
- equals
-
哪些包装类没有常量池?
- Float
- Double
8.4 String 类
8.4.1 String类的特点
特点:
-
Java程序中 ,所有双引号字符串,都是String这个类的对象
-
字符串一旦被创建,就不可以被更改,字符串内容不可变
如果想要更改,只能使用新的对象,做替换
-
String字符串虽然不可改变,但是可以共享
字符串常量池:当使用双引号创建字符串对象时,会检查常量池中是否存在该数据
不存在:创建
存在:复用
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
}
//true
8.4.2 String类的创建
- String s1 = “abc”;
- String s2 = new String(“abc”);
8.4.3 String类常量池
在Java7之前,字符串常量池位于方法区中
在Java7之后,字符串常量池被移入了堆内存中,而运行时常量池仍然位于方法区中
- 字符串都放在字符串常量池中
- 相同内容的字符串在常量池中只有一份
String s1 = "多喝热水";
String s2 = "多喝热水";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
System.out.println("------------------------------------");
String s3 = new String("多喝热水");
System.out.println(s1 == s3); //false
System.out.println(s1.equals(s3)); //true
System.out.println("------------------------------------");
String s4 = "多喝" + "热水";
System.out.println(s1 == s4); //true
System.out.println(s1.equals(s4)); //true
System.out.println("------------------------------------");
String s5 = "多喝";
String s6 = "热水";
String s7 = s5 + "热水";
System.out.println(s1 == s7); //false
System.out.println(s1.equals(s7)); //true
System.out.println("------------------------------------");
String s8 = s5 + s6;
System.out.println(s1 == s8); //false
System.out.println(s1.equals(s8)); //true
System.out.println("------------------------------------");
String s9 = "多" + "喝" + "热" + "水";
System.out.println(s1 == s9); //true
System.out.println(s1.equals(s9)); //true
-
-
true
8.4.4 String类遍历功能
- public char[] toCharArray() 将此字符串转换为一个新的字符数组
String s = "raining";
char[] chs = s.toCharArray();
for (char a : chs) {
System.out.println(a);
}
- public char charAt(int index) 返回指定索引处的 char 值
String s = "raining";
for (int i = 0; i < s.length(); i++) {
System.out.println(s.charAt(i));
}
- public int length() 返回字符串的长度(有"()")
8.4.5 String类判断功能
- public boolean euqals(Object obj)
- 判断字符串的内容是否相同
String s1 = "raining";
String s2 = new String("raining");
System.out.println(s1.equals(s2)); //true
- public boolean equalsIgnoreCase(String str)
- 忽略大小写判断字符串内容是否相等
String s1 = "raining";
String s2 = "Raining";
System.out.println(s1.equalsIgnoreCase(s2)); //true
- public boolean contains(String str)
- 判断字符串是否包含特定的字符串
String s1 = "raining";
System.out.println(s1.contains("rain")); //true
- public boolean startsWith(String str)
- 判断字符串是否以特定的字符串开头
String s1 = "raining";
System.out.println(s1.startsWith("rain")); //true
System.out.println(s1.startsWith("ain", 1)); //true
- public boolean endsWith(String str)
- 判断字符串是否以特定的字符串结尾
String s1 = "raining";
System.out.println(s1.endsWith("ning")); //true
- public boolean isEmpty()
- 判断字符串是否包含特定的字符串
System.out.println("".isEmpty()); //true
8.4.6 String类获取功能
- public int length()
- 获取字符串长度
String s = "raining";
System.out.println(s.length()); //7
- public char charAt(int index)
- 返回特定位置的索引
String s = "raining";
System.out.println(s.charAt(2)); //i
- public int indexOf(int ch)
- 返回特定字符首次出现的位置
String s = "raining";
System.out.println(s.indexOf('a')); //1
- public int indexOf(String str)
- 返回特定字符串首次出现的位置
String s = "raining";
System.out.println(s.indexOf("ai")); //1
- public int indexOf(int ch, int fromIndex)
- 返回特定字符首次出现的位置,从fromIndex开始
String s = "raining";
System.out.println(s.indexOf('i', 3)); //4
- public String substring(int start)
- 获取字串,从start索引开始
String s = "raining";
System.out.println(s.substring(0)); //raining
- public String substring(int start, int end)
- 获取字串,从start索引到end [start, end)
String s = "raining";
System.out.println(s.substring(0, 3)); //rai
8.4.7 String类转换功能
- public char[] toCharArray()
- 将字符串转化为char数组
String s = "raining";
System.out.println(s.toCharArray()); //raining
- public static String valueOf(char[] chs)
- 将字符串转化为String类
char[] chs = {'r', 'a', 'i', 'n','i', 'n', 'g'};
System.out.println(String.valueOf(chs)); //raining
- public String toLowerCase()
- 全部转换为小写字母
String s = "RAINING";
System.out.println(s.toLowerCase()); //raining
- public String toUpperCase()
- 全部转换为大写字母
String s = "raining";
System.out.println(s.toUpperCase()); //RAINING
- public String concat(String str)
- 字符串拼接
String s = "rain";
System.out.println(s.concat("ing")); //raining
8.4.8 String类替换功能
- String replace(char old, char new)
- 将old字符替换为new字符
String s = "raining";
System.out.println(s.replace('i', '*')); //ra*n*ng
- String replace(String old, String new)
- 将old字符串替换为new字符串
String s = "raining";
System.out.println(s.replace('ing', 'ed')); //rained
8.4.9 String类去空格功能
- trim()
- 去除首位空格
String s = " rainin g ";
System.out.println(s);
System.out.println(s.length());
System.out.println(s.trim());
System.out.println(s.trim().length());
/*
rainin g
13
rainin g
8
*/
8.4.10 可变字符串
- StringBuffer
- 字符串缓冲区,用于存储可变字符序列的容器
- 解决了String用字符串做拼接,既费时又耗内存的问题
- 可对字符串进行修改 - 动态拼接
- 长度可变
StringBuffer sbf = new StringBuffer("hello");
//拼接
sbf.append(" world");
sbf.append(" hahahahaha");
//StringBuffer -- String
str = sbf.toString();
System.out.println(str);
/*
hello world hahahahaha
*/
- StringBuilder
- 与StringBuffer功能相同
- 二者区别
- StringBuffer线程同步,线程安全,常用于多线程,效率低
- StringBuilder线程不同步,线程不安全,通常用于单线程,效率较高
8.5 数学相关类
8.5.1 Math类
位置:
- java.lang包
常量:
- PI 圆周率
- E 自然对数底数
System.out.println(Math.PI);
System.out.println(Math.E);
常用方法:
- random():返回带正号的double值,该值大于等于0.0且小于1.0 [0.0, 1.0)
//生成[0.0, 1.0)的随机数
for(int i = 0; i < 10; i++) {
System.out.println(Math.random());
}
System.out.println("-----------------------------");
//生成[0, 9]的随机数 - 整数
for(int i = 0; i < 10; i++) {
System.out.println((int)(Math.random() * 10));
}
System.out.println("-----------------------------");
//生成[1, 10]的随机数 - 整数
for(int i = 0; i < 10; i++) {
System.out.println((int)(Math.random() * 10) + 1);
}
System.out.println("-----------------------------");
//生成[1, 5]的随机数 - 整数
//[0, 4]
for(int i = 0; i < 30; i++) {
System.out.println((int)(Math.random() * 5) + 1);
}
8.5.2 BigDecimal类
位置:
- java.lang包
作用:
- 精确计算浮点整数
- 解决double存在的精度缺失问题
常用方法:
- add(BigDecimal augend)加
- subtract(BigDecimal subtrahend)减
- multiply(BigDecimal multiplicand)乘
- divide(BigDecimal divisor)除
进行除法运算时,如果不能准确的计算出结果时需要指定保留的位数和取舍方式。
divide(BigDecimal divisor, int scale, int roundingMode)
scale
:指定精确到小数点后几位mode
:指定小数部分的取舍模式,通常采用四舍五入的模式,取值为BigDecimal.ROUND_HALF_UP
public static void main(String[] args) {
double d = 10.0;
System.out.println(d / 3);
BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("3");
//加
BigDecimal d3 = d1.add(d2);
//BigDecimal -- int
System.out.println(d3.intValue());
//减
System.out.println(d1.subtract(d2));
//乘法
System.out.println(d1.multiply(d2));
//除法
System.out.println(d1.divide(d2, 5, BigDecimal.ROUND_HALF_UP));
}
/*
3.3333333333333335
13
7
30
3.33333
*/
8.6 日期时间相关类
8.6.1 Date
表示特定的瞬间,精确到毫秒。要注意该类中很多方法已经过时。
构造方法:
Date()
:分配Date
对象并用当前时间初始化此对象,以表示分配它的时间(精确到毫秒)Date(long date)
:分配Date
对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日 00:00:00 GMT)以来的指定毫秒数。
常用方法:
-
boolean after(Date anotherDate)
:测试此日期是否在指定日期之后 -
boolean before(Date anotherDate)
:测试此日期是否在指定日期之前 -
int compareTo(Date anotherDate)
:比较两个日期的顺序 -
long getTime()
:返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数 -
void setTime(long time)
:以long
类型参数time设置此Date对象,以表示1970年 1 月 1 日 00:00:00 GMT以后指定毫秒的时间点 -
String toString():默认实现是把此Date对象转换为以下形式的字符串
dow mon dd hh:mm:ss zzz yyyy
- dow是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
- mon是月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
- dd是一月中的某一天(01 至 31),显示为两位十进制数
- hh是一天中的小时(00 至 23),显示为两位十进制数
- mm是小时中的分钟(00 至 59),显示为两位十进制数
- ss是分钟中的秒数(00 至 61),显示为两位十进制数
- zzz是时区(并可以反映夏令时)。标准时区缩写包括方法 parse 识别的时区缩写。如果不提供时区信息,则 zzz 为空,即根本不包括任何字符
- yyyy是年份,显示为 4 位十进制数
public static void main(String[] args) {
//创建Date对象
Date date = new Date(); //当前时刻
System.out.println(date);
//获取时间戳
//什么是时间戳 - 从1970年1月1日00:00:00到现在经历的毫秒值
//Unix 1969/1970
System.out.println(date.getTime());
//另一种创建Date对象的方法
Date date1 = new Date(1706249472439L);
System.out.println(date1);
}
/*
Sat Jan 27 11:42:34 CST 2024
1706326954812
Fri Jan 26 14:11:12 CST 2024
*/
8.6.2 SimpleDateFormat
以指定格式输出日期和时间。
将Date
类型与字符串转换:
Date
转换成字符串:format()
- 字符串转为
Date
:parse()
public static void main(String[] args) throws ParseException {
Date date = new Date();
//使用XXXX年XX月XX日 XX:XX:XX形式输出当前时间
/*
* 对日期时间格式化操作
* 1. 创建SimpleDateFormat对象
* 2. 使用该对象的format方法进行格式化
* */
SimpleDateFormat sft = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//将Date对象格式化为特定格式的字符串
String dateStr = sft.format(date);
System.out.println(dateStr);
SimpleDateFormat sft1 = new SimpleDateFormat("yyyy年MM月dd日 KK:mm:ss a");
String dateStr1 = sft1.format(date);
System.out.println(dateStr1);
SimpleDateFormat sft2 = new SimpleDateFormat("HH:mm:ss");
String dateStr2 = sft2.format(date);
System.out.println(dateStr2);
/*
* 将特定格式的表示时间日期的字符串装换成Date对象
* SimpleDateFormat的parse()方法
*
* SimpleDateFormat的对象要和被转换的字符串兼容
* */
String dateStr3 = "2024年01月26日 14:25:14";
Date date1 = sft.parse(dateStr3);
System.out.println(date1);
}
/*
2024年01月27日 11:42:12
2024年01月27日 11:42:12 上午
11:42:12
Fri Jan 26 14:25:14 CST 2024
*/
8.6.3 Calendar
单独获取当前日期和时间中的年月日和星期。
获取Calendar对象:Calendar.getInstance()
常用字段:
Calendar.YEAR
年份Calendar.MONTH
月份,从0开始Calendar.DATE
日期Calendar.DAY_OF_MONTH
日期,和Calendar.DATE
完全相同Calendar.HOUR
12小时制的小时数Calendar.HOUR_OF_DAY
24小时制的小时数Calendar.MINUTE
分钟Calendar.SECOND
秒Calendar.DAY_OF_WEEK
星期几
常用方法:
set()
get()
public static void main(String[] args) {
//获取Calendar对象
Calendar c = Calendar.getInstance();
//System.out.println(c);
//get - 获取
System.out.println("年:" + c.get(Calendar.YEAR));
System.out.println("月:" + c.get(Calendar.MONTH) + 1);
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("星期:" + c.get(Calendar.DAY_OF_WEEK)); //星期天开始的
//set - 设置
c.set(Calendar.YEAR, 2024); //2024年
c.set(Calendar.MONTH, 4); //5月
c.set(Calendar.DAY_OF_MONTH, 1); //1日
System.out.println("星期:" + c.get(Calendar.DAY_OF_WEEK));
}
/*
年:2024
月:01
日:27
星期:7
星期:4
*/
打印万年历
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
/*
* 输入年、月,打印当月的万年历
* 1. 确定当月有多少天
* 2. 当月的第一天是周几
*
* */
System.out.print("请输入年:");
int year = sc.nextInt();
System.out.print("请输入月:");
int month = sc.nextInt();
//该月的总天数
int days = 0;
//确定当月一共有几天
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
days = 29;
} else {
days = 28;
}
break;
}
//当月的第一天是周几
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, 1);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); //从周日开始
int count = 0; //用来控制换行的计数器
System.out.println("日\t一\t二\t三\t四\t五\t六");
//先输出1号之前的空格
for (int i = 0; i < dayOfWeek - 1; i++) {
System.out.print('\t');
count++;
}
//输出日期
for (int i = 1; i <= days; i++) {
System.out.print(i + "\t");
count++;
if(count % 7 == 0) {
System.out.println();
}
}
}
/*
请输入年:2024
请输入月:1
日 一 二 三 四 五 六
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
*/
8.6.4 JDK8中新日期时间API
8.6.4.1 概述
JDK 1.0中包含了 一个java.util.Date
类,但是它的大多数方法已经在JDK 1.1引入Calendar
类之后被弃用 了。而Calendar
并不比Date
好多少,它们面临的问题是:
- 可变性:像日期和时间这样的类应该是不可变的;
- 偏移性:
Date
中的年份是从1900开始的,而月份都从0开始; - 格式化:格式化只对
Date
有用,Calendar
则不行; - 此外,它们也不是线程安全的;不能处理闰秒等。
对日期和时间的操作一直是Java程序员最痛苦的地方之一。
Java 8吸收了Joda-Time的精华,以一个新的开始为Java创建优秀的API。 新的java.time
中包含了所有关于本地日期LocalDate
、本地时间LocalTime
、本地日期时间LocalDateTime
、时区ZonedDateTime
和持续时间Duration
的类。历史悠久的Date
类新增了toInstant()
方法, 用于把Date
转换成新的表示形式。这些新增的本地化时间日期API大大简化了日期时间和本地化的管理。
8.6.4.2 LocalDate
、LocalTime
、LocalDateTime
LocalDate
、LocalTime
、LocalDateTime
类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
LocalDate
代表IOS格式yyyy-MM-dd
的日期,可以存储 生日、纪念日等日期;LocalTime
表示一个时间,而不是日期;LocalDateTime
是用来表示日期和时间的,这是一个最常用的类之一。
方法 | 描述 |
---|---|
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() | 从当前对象减去几月、几周、几天、几年、几小时 |
public static void main(String[] args) {
//创建LocalDateTime对象 -- 当前的时刻
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);
//创建LocalDateTime对象 -- 特定的时刻
LocalDateTime dateTime1 = LocalDateTime.of(2024, 2, 10, 10, 10, 10);
System.out.println(dateTime1);
System.out.println(dateTime.getDayOfMonth()); //26
System.out.println(dateTime.getDayOfYear()); //26
System.out.println(dateTime.getDayOfWeek().getValue());
System.out.println(dateTime.getMonth().getValue());
System.out.println(dateTime.getHour());
System.out.println(dateTime.getMinute());
System.out.println(dateTime.getSecond());
LocalDateTime dateTime2 = dateTime.withMonth(5);
LocalDateTime dateTime3 = dateTime.withDayOfMonth(30);
System.out.println(dateTime);
System.out.println(dateTime2);
System.out.println(dateTime3);
LocalDateTime dateTime4 = dateTime.plusDays(1);
System.out.println(dateTime4);
}
/*
2024-01-27T11:58:09.378
2024-02-10T10:10:10
27
27
6
1
11
58
9
2024-01-27T11:58:09.378
2024-05-27T11:58:09.378
2024-01-30T11:58:09.378
2024-01-28T11:58:09.378
*/
8.6.4.3 格式化与解析日期或时间
DateTimeFormatter
提供了三种格式化方法:
- 预定义的标准格式
- 自定义的格式
- 本地化相关的格式
public static void main(String[] args) {
/*
* 格式化日期时间 -- 产生特定格式的字符串
* */
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String dateTimeStr = dateTime.format(fmt);
System.out.println(dateTimeStr);
//xx时xx分xx秒
DateTimeFormatter fmt1 = DateTimeFormatter.ofPattern("HH时mm分ss秒");
String dateTimeStr1 = dateTime.format(fmt1);
System.out.println(dateTimeStr1);
String dateTimeStr2 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(dateTimeStr2);
String dateTimeStr3 = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
System.out.println(dateTimeStr3);
//将特定格式的字符串转换成日期时间对象
LocalDateTime localDateTime = LocalDateTime.parse("2024年01月26日 16:03:26", fmt);
System.out.println(localDateTime);
LocalDateTime localDateTime1 = LocalDateTime.parse("2024-01-26T16:07:36.644");
System.out.println(localDateTime1);
}
/*
2024-01-27T12:00:05.830
2024年01月27日 12:00:05
12时00分05秒
20240127
2024-01-27T12:00:05.83
2024-01-26T16:03:26
2024-01-26T16:07:36.644
*/
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
//LocalDateTime -- 特定格式的字符串
String str = fmt.format(dateTime);
System.out.println(str);
//特定格式的字符串 -- LocalDateTime
TemporalAccessor temporalAccessor = fmt.parse("2024年01月26日 16:25:46");
//TemporalAccessor -- LocalDateTime
LocalDateTime dateTime1 = LocalDateTime.from(temporalAccessor);
System.out.println(dateTime1);
}
/*
2024年01月27日 12:00:33
2024-01-26T16:25:46
*/
九、三个修饰符
9.1 final 关键字 – 最终的
-
表示最终的
-
作用:可以修饰变量、修改方法、修饰类
-
修饰变量 – 变为常量
-
这个变量就变成了常量
-
定义是可以不初始化,但是只要使用必须初始化,初始化之后不能修改值
-
常量所有字母都要大写,单词之间使用_分隔
MAX_AGE PI E
- final如果修饰引用数据类型,地址不可变
-
-
修饰方法 – 最终的方法 – 不能被重写
-
修饰类 – 最终的类 — 不能被继承
-
9.2 abstract 抽象 不具体
9.2.1 抽象类:
需要设计这个类,通常充当父类,但是不需要创建对象 – 设计为抽象类
抽象类
* 1. 什么样的类要被设计为抽象类?
* 作为父类,被其它类继承,不需要创建对象
* 2.如何设计一个抽象类
* 使用修饰符abstract
* public abstract class 类名 {
*
* }
* 3.抽象类无法实例化
* 4.可以在抽象类中设计构造方法,但是不能通过构造方法创建对象
9.2.2 抽象方法
-
什么样的方法要被定义为抽象方法?
需要定义,但是不需要方法体
-
如何定义抽象方法?
- 使用abstract关键字
- 没有方法体,{} 用;代替
- 只有方法声明,没有方法实现
-
抽象类和抽象方法的关系
-
有抽象方法的类,一定是抽象类
-
抽象类中不一定要有抽象方法
-
如果一个类继承了抽象类,那么就要重写所有的抽象方法
如果子类没有重写所有的抽象方法,那么子类也要定义为抽象类
-
9.3 static 静态
9.3.1 何为静态
static – 静态的
- 可以修饰属性和方法
- 被static修饰的属性在内存中只有一份
- 被static修饰的属性或方法如何访问:
1. 类名.成员 //建议使用
2. 对象名.成员 - 如果修改了被static修饰的属性的值,那么所有的对象都会被观察到
- static修饰的属性随着类的加载而加载,和是否创建对象无关 (不创建对象,static也存在)
实例属性只能创建对象之后才能存在
9.3.2 实例属性(没有被static修饰的属性)和静态属性对比
- 实例属性是每个对象独有的,修改了一个对象的实例属性,对其他同名实例属性没有影响 – 拥有自己独立的空间
- 静态属性在内存中只有一份,是属于类的,修改了该属性的值,任何的对象都会观察到对该属性的修改
9.3.3 静态方法
- 什么是静态方法?就是被static修饰的方法
- 如何调用静态方法?
- 类名.方法名
- 对象名.方法名
- static修饰方法可以访问当前类被static修饰的属性,不能直接访问当前类的实例属性
static的能够直接访问static的 - static修饰的方法不能出现this和super关键字
9.3.4 动态代码块
代码块 { } 方法 类 if switch 循环
动态代码块和静态代码块
共性:和属性、方法、构造方法平级
代码块:使用{ } 括起来的代码被称为代码块
动态代码块(构造代码块)
位置:类中方法外的一对大括号
- 创建对象时被调用,创建一次,调用一次
- 代码块执行和实例属性初始化优先于构造方法执行
- 代码块执行和实例属性初始化的顺序由代码编写的顺序决定,写在前面的先运行
- 应用:
- 可为实例属性赋值,或必要的初始行为
- 将多个构造方法中,重复的代码,抽取到构造代码块中,从而提升代码的复用性
public class test {
{
//动态代码块
}
public static void main(String[] args) {
}
}
9.3.5 静态代码块
位置:类中方法外的一对大括号,需要加入static关键字
- 类加载时被调用,只会调用一次
- 静态代码块和静态属性初始化优先于构造方法执行
- 静态代码块和静态属性初始化的顺序由代码编写的顺序决定,写在前面的先执行
- 应用:
- 可为静态属性赋值,或必要的初始行为
- 对数据进行初始化
static {
//静态代码块
}
9.3.6 局部代码块
位置:方法中的一对大括号
作用:限定变量的生命周期,提早的释放内存
void method() {
{
//局部代码块
int a = 10;
}
}
9.3.7 对象创建过程
public class X {
public X() {
System.out.println("实例属性");
}
}
public class Y {
public Y() {
System.out.println("静态属性");
}
}
public class MyClass3 {
private X x = new X();
private static Y y = new Y();
//动态代码块
{
System.out.println("动态代码块");
}
//静态代码块
static {
System.out.println("静态代码块");
}
public MyClass3() {
System.out.println("构造方法...");
}
}
public class MyTest5 {
public static void main(String[] args) {
MyClass3 c1 = new MyClass3();
System.out.println("==========================");
MyClass3 c2 = new MyClass3();
}
}
/*
静态属性
静态代码块
实例属性
动态代码块
构造方法...
==========================
实例属性
动态代码块
构造方法...
*/
9.3.8 带有继承的对象创建过程
public class X {
public X() {
System.out.println("父类实例属性");
}
}
public class Y {
public Y() {
System.out.println("父类静态属性");
}
}
public class M {
public M() {
System.out.println("子类实例属性");
}
}
public class N {
public N() {
System.out.println("子类静态属性");
}
}
public class SuperClass {
private X x = new X();
private static Y y = new Y();
//动态代码块
{
System.out.println("父类动态代码块");
}
//静态代码块
static {
System.out.println("父类静态代码块");
}
public SuperClass() {
System.out.println("父类构造方法...");
}
}
public class SubClass extends SuperClass {
private M m = new M();
private static N n = new N();
//动态代码块
{
System.out.println("子类动态代码块");
}
//静态代码块
static {
System.out.println("子类静态代码块");
}
public SubClass() {
System.out.println("子类构造方法...");
}
}
public class MyTest6 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println("***************************");
SubClass subClass1 = new SubClass();
}
}
/*
父类静态属性
父类静态代码块
子类静态属性
子类静态代码块
父类实例属性
父类动态代码块
父类构造方法...
子类实例属性
子类动态代码块
子类构造方法...
***************************
父类实例属性
父类动态代码块
父类构造方法...
子类实例属性
子类动态代码块
子类构造方法...
*/
代码块和属性的执行顺序和在类中定义的顺序有关
9.3.7 单例设计模式
什么是设计模式? – 解决问题的套路
1.单例设计模式 – 单个 一个 – 符合单例设计模式的类,对象只能创建一个
God Sun Moon 有且只能有一个
配置类 维护程序的配置 只能有一个
2.如何使既符合单例设计模式的类?
之前是如何创建对象的? new + 构造方法();
之前为什么能够创建多个对象? 构造方法可以被调用多次
关键:构造方法只能被调用一次/构造方法不能随便被调用
3.如何判断两个对象是同一个对象?/如何判断两个引用指向同一个对象?
== 的作用
- 判断基本数据类型的值是否相等
- 判断两个对象是否为同一个对象/判断两个引用是否指向同一个对象
//符合单例设计模式的类 饿汉式
public class SingleObj1 {
private static SingleObj1 obj = new SingleObj1();
private SingleObj1() {
}
public static SingleObj1 getInstance() {
return obj;
}
}
/*
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
//单例设计模式 -- 懒汉式
public class SingleObj2 {
private static SingleObj2 obj;
public SingleObj2() {
}
public static SingleObj2 getInstance() {
if (obj == null) {
obj = new SingleObj2();
}
return obj;
}
}
/*
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
public class MyTest {
public static void main(String[] args) {
//SingleObj1 obj1 = new SingleObj1(); //不能被调用
SingleObj1 obj1 = SingleObj1.getInstance();
SingleObj1 obj2 = SingleObj1.getInstance();
//如何判断两个对象是同一个对象?
System.out.println(obj1 == obj2); //true
SingleObj2 obj3 = SingleObj2.getInstance();
SingleObj2 obj4 = SingleObj2.getInstance();
System.out.println(obj3 == obj4); //true
}
}
可以在更多工具的TODO中找到
-site:youkuaiyun.com 不想搜索到的关键字
暂时无法完成的内容,加
//TODO
十、接口和内部类
10.1 何为接口
Java接口是一系列方法的声明,是一些方法特征的集合,**一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)**。
接口可以理解为一种特殊的类,里面全部是由***全局常量***和**公共的抽象方法**所组成。接口是解决***Java无法使用多继承***的一种手段,但是接口在实际中更多的作用是***制定标准***的。或者我们可以直接把接口理解为***100%的抽象类***,既接口中的方法**必须全部**是抽象方法。(JDK1.8之前可以这样理解)
接口
1.生活中的接口?
网口、USB接口、Type-C、VGA、HDMI
1.“规范”
2.“能力”
2.Java接口
1.“规范”
2.“能力” **能不能**
声明规则/规范
10.2 接口定义
[修饰符] interface 接口名 {
常量;
抽象方法;
}
-
修饰符 public
-
接口使用interface关键字定义
-
接口名,起名字,要符合标识符的命名规范
-
接口中定义的属性都是常量,默认使用public static final 修饰
public static final 默认有,可以不写
int a = 10; -
接口中的方法都是抽象方法,默认使用public abstract修饰
public abstract 默认有,可以不写
int test(); -
接口没有构造方法 – 接口不能创建对象
-
接口可以继承接口,并且支持多继承
交通工具
1.交通工具 - 能力
2.加油 - 能力
10.3 如何使用
使用接口(如何实现接口 - 实现类实现接口 - 定义接口的实现类)
- 类 - 继承
- 接口 - 实现
[修饰符] class 实现类类名 implements 接口名1, 接口名2, 接口名3 {
属性
方法
}
public class Car implements Vehicle, Energy {
}
- 修饰符 public
- 实现类类名,起名字,符合标志符的命名规则
- 一个实现类可以实现多个接口
- 如果一个类实现了接口,那么就要重写接口的所有抽象方法
如果实现类没有重写所有的抽象方法,那么这个实现类就要被定义为抽象类 - 如果实现类既要继承父类,又要实现接口 – 先继承,再实现
[修饰符] class 实现类类名 extends 父类 implements 接口名1, 接口名2, 接口名3 {
属性
方法
}
public class Car extends Cars implements Vehicle, Energy {
}
类和接口的关系
- 类实现接口
- 一个类可以实现多个接口
- 接口能继承接口,能继承多个接口
10.4 Java8关于接口的升级
JDK8接口特性
-
允许在接口中定义非抽象方法,但是需要使用关键字default修饰,这些方法就是默认方法
-
作用:解决接口升级的问题
-
注意事项:
- public可以省略,但是default不能省略
- 默认方法,实现类允许重写(不强制重写),但是要去掉default修饰符
- 如果实现了多个接口,多个接口中存在相同的默认方法,实现类必须重写默认方法
默认方法定义格式
-
public default 返回值类型 方法名() {
}
public default void show() {}
-
允许定义静态方法
-
理解:既然接口已经允许带有方法体了,干脆也放开静态方法,可以类名调用
-
注意事项:
-
public可以省略,但是static不能省略
-
接口中的静态方法,只允许接口名进行调用,不允许实现类通过对象调用
接口.静态方法();
-
-
JDK9接口特性
-
接口中允许定义私有方法 - 提升复用性,减少代码冗余
-
接口升级,不能兼容已经存在的实现类的代码,必须对已经存在的实现类代码进行修改 – 需要修改的地方太多,成本很高
– 希望达到的效果 - 既要对接口进行升级,有对已经存在的实现类代码不会产生影响
–默认方法:
1. 被default关键字修饰的方法,可以有方法体
2. 通过实现类对象调用
3. 如果默认方法不能满足实现类的要求,就需要在实现类中对默认方法进行重写
静态方法 - 被static修饰 – 通过接口.静态方法()调用
可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
public interface A {
double PI = 3.14; //常量
default void m1() {
System.out.println("test1...");
}
public static void m2() {
System.out.println("test2...");
}
}
public class MyClass implements A {
}
public class MyTest3 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.m1(); //通过实现类对象来调用
A.m2(); //调用接口中的静态方法
}
}
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
public interface M {
default void test() {
System.out.println("M....");
}
}
public interface N {
default void test() {
System.out.println("N....");
}
}
public class MyClass1 implements M, N {
//实现类必须覆盖接口中同名同参数的方法,来解决冲突
@Override
public void test() {
M.super.test();
N.super.test();
System.out.println("MyClass1....");
}
}
public class MyTest4 {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
myClass1.test();
}
}
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
public class SuperClass {
public void test() {
System.out.println("SuperClass....");
}
}
public class MyClass2 extends SuperClass implements M, N {
}
public class MyTest5 {
public static void main(String[] args) {
MyClass2 myClass2 = new MyClass2();
myClass2.test();
}
}
10.5 接口多态
- 关于继承的多态
子类对象赋值给父类引用/父类引用指向子类对象 - 关于接口的多态
实现类对象赋值给接口引用/接口引用指向实现类对象
Vehicle v1 = new Car();
Vehicle v2 = new Fighter();
编译时 - 看左边 - 在编译时,左边有的方法才可以调用
运行时 - 看右边 - 运行期间,实际运行的是右边对象拥有的方法 - 接口多态的应用
- 接口类型作为方法的参数
- 接口类型作为方法的返回值
- 向下转型
(类型)引用
注意:向下转型要先判断引用本身的类型 - instanceof
public class MyTest {
public static void main(String[] args) {
Vehicle v1 = new Car();
Vehicle v2 = new Fighter();
test(v1);
test(v2);
}
public static void test(Vehicle v) {
if (v instanceof Car) {
Car c = (Car) v;
c.didi();
} else if (v instanceof Fighter) {
Fighter f = (Fighter) v;
f.fight();
}
}
}
public static Vehicle create(String name) {
switch (name) {
case "car":
return new Car();
case "fighter":
return new Fighter();
default:
return new Car();
}
}
10.6 内部类
10.6.1 何为内部类
- 定义在一个类里面的类
class Outer { //外部类
//内部类
class Inner {
}
}
- 创建对象的格式
//格式:外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
Outer.Inner in = new Outer().new Inner();
- 成员访问细节
- 内部类中,访问外部类成员 : 直接访问,包括私有
- 外部类中,访问内部类成员 : 需要创建对象访问
- 在成员类中,访问所在外部类对象,格式:外部类名.this
public class MyTest1 {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
System.out.println(oi.num);
oi.show();
}
}
class Outer {
int num = 10;
private void method() {
System.out.println("method...");
Inner i = new Inner();
System.out.println(i.num);
}
class Inner {
int num = 20;
public void show() {
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(Outer.this.num); //10
System.out.println("show...");
method();
}
}
}
/*
20
30
20
10
show...
method...
20
*/
- 静态内部类 static修饰的成员内部类
class Outer {
static class Inner {
}
}
- 创建对象格式
//格式:外部类名.内部类名 对象名 = new 外部类对象.内部类对象();
Outer.Inner in = new Outer.Inner();
- 静态只能访问静态
public class MyTest1 {
public static void main(String[] args) {
Outer.Inner.show();
}
}
class Outer {
int num1 = 10;
static int num2 = 20;
static class Inner {
public static void show() {
System.out.println("show...");
Outer outer = new Outer();
//静态无法直接访问非静态
System.out.println(outer.num1);
System.out.println(num2);
}
}
}
/*
show...
10
20
*/
- 局部内部类 放在方法、代码块、构造器等执行体中
public class MyTest1 {
public static void main(String[] args) {
//Outer.Inner.show();
A a = new A();
a.show();
}
}
class A {
public void show() {
class B {
public void method() {
System.out.println("method...");
}
}
B b = new B();
b.method();
}
}
内部类 - 在类的内部定义的类 - 匿名内部类
场景:类的对象只会创建一次,以后再也不会创建这个类的对象了 – 代码结构变得复杂
10.6.2 匿名内部类
本质:一个特殊的局部内部类(定义在方法内部)
前提:需要存在一个接口或类
格式:
new 类名/接口名() {
}
/*
new 类名() {} : 代表继承这个类
new 接口名() {} : 代表实现这个接口
*/
个人理解:
我们现在有一个方法,我们希望传入一个接口类型,由于接口类无法创建实例,所以我们只能传入该接口的实现类对象
1. 我们要创建一个实现类
1. 实现类中重写接口的抽象方法
1. 在调用的时候创建一个实现类对象,传入到方法中,运用多态的原理
public class MyTest1 {
public static void main(String[] args) {
useInter(new InterImpl());//传入的是新建的实现类对象
}
public static void useInter(Inter i ) {
i.show();
}
}
interface Inter {
void show();
}
//创建接口的实现类
class InterImpl implements Inter {
@Override
public void show() {
System.out.println("InterImpl...show...");
}
}
假若我们使用匿名内部类的话,只需要一步就可以完成
public class MyTest1 {
public static void main(String[] args) {
useInter(new Inter() {
@Override
public void show() {
System.out.println("匿名内部类");
}
});
}
public static void useInter(Inter i ) {
i.show();
}
}
interface Inter {
void show();
}
public static void main(String[] args) {
/*
* {} - 匿名内部类的定义
* new ... - 匿名内部类的对象
* */
test(new Vehicle() {
@Override
public void start() {
System.out.println("start...");
}
@Override
public void run() {
System.out.println("run...");
}
@Override
public void stop() {
System.out.println("stop...");
}
});
}
10.7 包
10.7.1 问题引入
苹果 Apple.java
苹果手机 Apple.java
- 没有包存在的问题?
无法创建同名的Java文件
|
想一种方式能够对同名文件进行区分
|
放在不同的文件夹中
10.7.2 何为包
包(package):Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。
- 本质:文件夹/目录,功能相似的类放在同一目录下;
- 对类进行了包装,在不同的包中允许有相同类名存在,在一定程度上可以避免命名冲突
10.7.3 包的使用
- 声明包
package 包名;
告诉编译器当前的类应该在哪个包下 – 报错 - 使用包
什么时候用到? 使用到了特定的类时,就需要使用import导入
全类名 = 包名 + 类名;
-
import 全类名;
- import java.util.Scanner;
-
import 包名.*;
- import java.util.*;
-
java.lang包是系统默认导入的包,不需要手动导入
System String- JDK 常用包介绍
java.lang
:包括了Java语言程序设计的基础类;
java.util
:包含集合、日期和各种实用工具类;
java.io
:包含可提供数据输入、输出相关功能的类;
java.net
:提供用于实现Java网络编程的相关功能类;
java.sql
:提供数据库操作相关功能类。
注意:java.lang
是默认会导入的包,不需要手动导入。
十一、集合
11.1 何为集合
ArrayList
存放对象的容器,长度可变,定义了对多个对象操作的常用方法,可以实现类似数组的功能
与数组的区别:
- 数组长度固定,集合长度可变
- 初始化之后不能动态修改长度
- 数组可以存放基本数据类型和引用数据类型,集合只能存放引用数据类型
ArrayList长度可变原理:
- 当创建ArrayList集合容器的时候,底层会存在一个长度为10个大小的数组
- 当长度不满足要求时,扩容原数组1.5倍大小的新数组
- 将原数组数据,拷贝到新数组中
- 将新元素添加到新数组中
集合与数组的使用选择:
- 数组:存储的元素个数固定不变
- 集合:存储的元素个数经常发生改变
11.2 如何使用
-
构造方法:
public ArrayList():创建一个空的集合容器 -
集合容器的创建细节:
ArrayList list = new ArrayList();- 现象:可以添加任意类型数据
- 弊端:数据不严谨
-
ArrayList常用方法
-
增
- public void add(E element)
- 添加指定元素
- public void add(int index, E element)
- 在指定索引位置,添加对应的元素
- public boolean addAll(Collection<? extends E> c)
- 添加新的集合到末尾
- public void add(E element)
-
```java
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add(3, "d");
System.out.println(list);
/*
[a, b, c, d]
*/
ArrayList<String> list1 = new ArrayList<>();
list1.add("e");
list1.add("f");
/*
[a, b, c, d, e, f]
*/
-
删
- public E remove(int index)
- 根据索引做删除,返回被删除掉的元素
- public boolean remove(Obejct o)
- 根据元素做删除,返回是否删除成功的状态
System.out.println(list); list.remove(0); System.out.println(list); list.remove("b"); System.out.println(list); /* [a, b, c, d] [b, c, d] [c, d] */
- public E remove(int index)
-
改
- public E set(int index, E element)
- 修改指定索引位置,改为对应元素,返回被覆盖掉的元素
System.out.println(list); list.set(0, "a"); System.out.println(list.set(1, "b")); System.out.println(list); /* [c, d] d [a, b] */
- public E set(int index, E element)
-
查
-
public E get(int index)
- 根据索引,获取集合中的元素
-
public int size()
- 返回集合中元素的个数
-
public boolean contains(Object o)
- 判断集合中是否包含该元素
- 需要重写集合元素中的equals方法
-
public int indexOf(Object o)
- 返回首次出现该元素的下标
-
public int lastIndexOf(Object o)
- 返回最后一次出现该元素的下标
System.out.println(list); System.out.println(list.get(0)); System.out.println(list.size()); /* [a, b] a 2 */ boolean a = list.contains("a"); System.out.println(a); //true
-
-
ArrayList遍历
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
- 普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
- 增强for循环
for (String s : list) {
System.out.println(s);
}
-
使用迭代器遍历,步骤:
-
获取迭代器
-
使用迭代器的方法遍历
-
hasNext() - 判断集合中是否还有元素可以遍历
-
next() - 返回可以迭代的下一个元素
-
- 迭代器一旦迭代完成之后,就不能重复使用,必须重新生成一个新的迭代器
-
//获取迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) { //判断集合中是否还有元素可以遍历
String next = iterator.next(); //返回可以迭代的下一个元素
System.out.println(next);
}
- 删除集合中的某个元素
- 注意事项:
不要使用集合自身的方法对集合进行修改,要使用迭代器的方法对集合进行修改
- 注意事项:
Iterator<String> it1 = list.iterator();
while (it1.hasNext()) { //判断集合中是否还有元素可以遍历
String next = it1.next(); //返回可以迭代的下一个元素
if (next.equals("5")) {
//list.remove("5"); //会出错
it1.remove();
}
}
- ListIterator是List特有的迭代器
- 可以对List进行遍历
ListIterator<String> listIterator = list.listIterator();
System.out.println("判断后面是否还有元素: " + listIterator.hasNext()); //true
System.out.println("判断后面是否还有元素: " + listIterator.hasPrevious()); //false
System.out.println("--------------------------------------------");
while (listIterator.hasNext()) {
String s = listIterator.next();
System.out.println(s);
}
while (listIterator.hasPrevious()) {
String s = listIterator.previous();
System.out.println(s);
}
System.out.println("--------------------------------------------");
//从后往前遍历
ListIterator<String> listIterator1 = list.listIterator(list.size());
while (listIterator1.hasPrevious()) {
String s = listIterator1.previous();
System.out.println(s);
}
- 使用Stream流遍历
list.stream().forEach(item -> System.out.println(item));
list.stream().forEach(System.out::println);
- 使用forEach方法
list.forEach(item -> System.out.println(item));
11.3 集合框架
集合框架
集合框架的体系结构 – “会用”
-
Collection(接口)单列集合:一次添加一个元素
-
List(接口) 元素有下标,可以重复 ;存取有序、有索引、可以存储重复的
- ArrayList 基于数组,通常用在查询频繁的场合,线程不安全
- Vector 基于数组,通常用在查询频繁的场合,线程安全
- LinkedList 基于链表,通常用作增删改查频繁的场合
-
Set(接口) 元素没有下标,不可以重复;存取无序、没有索引、不可以存储重复的
- HashSet
- LinkedHashSet
- TreeSet
-
-
Map(接口)每个元素可以分成key和value两个部分 k - v 键值对 双列集合:一次添加两个元素
- HashMap
- LinkedHashMap
- TreeMap
- Properties
使用 – “增删改查”
11.4 泛型
引入泛型
<> :泛型
-
作用:
- 类型参数化
- 使用泛型,可以对集合中存储的数据,进行类型限制
- 能够约束集合中的元素类型
-
细节:反省中,不允许编写基本数据类型
-
问题:怎样存储基本数据类型?
-
解决:使用基本数据类型所对应的包装类
-
如何使用:
- 根据已经定义好的类使用泛型
- 在自定义类和接口中使用泛型
-
泛型类:在类后面加泛型,对类型进行约束
- T - type - 类型
- 能够兼容多种类型
- 能够对类型进行约束
- 就表示泛型,所有使用到T的位置,都有受T代表的类型的约束
- 这个字母可以是任意字母
- <>中可以定义多个泛型
- 泛型方法不一定在泛型类中
-
泛型方法
- 不希望受到类上的泛型约束
- 还希望有泛型对类型约束的功能
- 泛型定义在返回值之前,泛型由方法的参数决定
- 泛型方法不一定在泛型类当中
ArrayList list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);
System.out.println(list.size());
ArrayList<Double> list1 = new ArrayList<>();
list1.add(11.1);
list1.add(22.2);
list1.add(33.3);
System.out.println(list1);
/*
[张三, 李四, 王五]
3
[11.1, 22.2, 33.3]
*/
11.5 Collections工具类
- Collections是JDK提供的工具类,位于java.util包中,提供了一系列静态方法,能更方便地进行各种操作
11.5.1 问题引入
在比较java中的对象时,我们通常只能使用 == 或 != 对地址进行比较,或者是通过改写后的equals对对象某一属性的内容进行是否相同的比较,无法进行大小的比较,因此无法满足我们的要求,所以我们需要一种方法来对对象进行比较排序。
Comparable(自然排序)接口和Comparator(定制排序)接口可以实现我们的要求。
11.5.2 Compareable接口 – 自然排序
Comparable接口强行对实现它的每个类的对象进行整体排序,这种排序被称为类的自然排序
-
自然排序的实现步骤
- 实现Comparable接口,重写compareTo(Object obj)方法,该方法规则:
- 如果当前对象this比obj大,该方法返回正值;
- 如果当前对象this比obj小,该方法返回负值;
- 如果当前对象this等于obj,该方法返回0;
- 实现Comparable接口,重写compareTo(Object obj)方法,该方法规则:
-
compareTo(Object obj)方法要在待排序的对象类中进行重写
-
注意事项:
- String类、包装类等实现了对Comparable接口,重写了compareTo方法,给出比较两个对象大小的方式
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo方法,并在compareTo方法中指明如何排序
- 实现Comparable接口的对象列表或和数组,可以通过Collections.sort() 或 Arrays.sort() 进行自动排序
-
我的理解:
-
首先,Comparable接口没有提供可以直接排序的方法,所以我们需要在我们定义的对象类中实现该接口,并实现该接口中的抽象方法compareTo,我们再使用Collections.sort()对list进行排序时,默认调用重写的方法,所以我们需要将重点放在自定义的类中
- 自定义类实现Comparable接口
- 在自定义类中按照需要重写compareTo方法
-
//自定义类实现Comparable接口 public class Car implements Comparable<Car> { //在自定义类中按照需求重写compareTo方法 @Override public int compareTo(Car o) { return this.brand.compareTo(o.brand) != 0 ? this.brand.compareTo(o.brand) : this.type.compareTo(o.type); } } //使用 Collections.sort(list);
-
11.5.3 Comparator接口 – 定制排序
当元素的类型没有实现
java.lang.Comparable
接口而又不方便修改代码,或者实现了java.lang.Comparable
接口的排序规则不适合当前的操作,那么可以考虑使用Comparator
的对象来排序,强行对多个对象进行整体排 序的比较,这种排序称为定制排序。
-
定制排序的实现步骤:
- 实现Comparetor接口,重写compare(Object o1, object o2)方法,该方法规则:
- 如果当前对象o1比o2大,该方法返回正值;
- 如果当前对象o1比o2小,该方法返回负值;
- 如果当前对象o1等于o2,该方法返回0;
- Comparator接口通过匿名内部类实现
- 实现Comparetor接口,重写compare(Object o1, object o2)方法,该方法规则:
-
我的理解:
-
Comparator接口的使用条件是当前没有实现Comparable接口或者实现的Comparable接口无法满足当前排序的规则,因此,为了实现当前的排序要求,我们需要通过Comparator接口来对本次排序进行规则定制,重点如下:
- 使用匿名内部类实现
- 按照要求的排序规则对compare方法进行重写
-
//按照字符串长度升序排序 Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { //比较两个字符串的长度 //return o1.length() - o2.length(); return Integer.compare(o1.length(), o2.length()); } });
-
11.5.4 Collectiongs类常用的方法:
-
public static void sort(List list)
-
按照字典升序进行排序
-
Collections.sort(list);
-
-
public static void reverse(List<?> list)
-
将集合进行逆序/反转
-
Collections.reverse(list);
-
-
public static void shuffle(List<?> list)
-
将集合打乱
-
Collections.shuffle(list);
-
-
public static void swap(List<?> list, int i, int j)
-
将集合list中的i 和 j 元素交换位置
-
Collections.swap(list, 0, list.size() - 1);
-
-
public static T max(List<?> list)
-
获取集合中最大的元素
-
String s = Collections.max(list); //获取长度最大的元素 s = Collections.max(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
-
-
public static T min(List<?> list)
-
获取集合中最小的元素
-
String s = Collections.mmin(list);
-
-
public static int frequency(Collection<?> c, Object o)
-
获取指定元素在集合中出现的次数
-
System.out.println(Collections.frequency(list, "aba"));
-
-
public static void copy(List<? super T> dest, List<? extends T> src)
-
复制集合 - 目的集合元素个数要大于等于源集合元素个数
-
ArrayList<String> list1 = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { list1.add(null); } Collections.copy(list1, list); System.out.println(list1);
-
-
public static boolean replaceAll(List list, T oldVal, T newVal)
-
替换集合中的元素
-
Collections.replaceAll(list, "111", "222");
-
11.6 Set接口
11.6.1 何为Set
Set接口是Collectons接口的子接口,Set接口没有提供额外的方法
Set集合不允许包含相同的元素
Set判断两个对象是否相同不是使用==运算符,而是equals()方法
11.6.2 Set接口实现类比较
- HashSet作为Set接口的主要实现类,线程不安全,可以存储null
- LinkedHashSet作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet
- TreeSet可以按照添加对象的指定属性进行排序
11.6.3 Set遍历
public class MyTest14 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("JavaSE");
set.add("MySQL");
set.add("JDBC");
set.add("JavaWeb");
set.add("JDBC");
//使用增强for循环遍历
for(String item : set) {
System.out.println(item);
}
System.out.println("-------------------------------");
//使用迭代器遍历
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
}
}
11.6.4 HashSet
HashSet是Set接口的经典实现,大多数时候使用Set集合时,都是用HashSet实现类
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除的性能
-
HashSet特点:
- 不能保证元素的排列顺序
- 元素不能重复
- HashSet不是线程安全的
- 集合元素可以是null
-
HashSet的无序性 - 添加的顺序与遍历的顺序不一致
-
每次便利的顺序是一致的但是与添加的顺序不一致
-
无序不是随机
-
数据存储在底层数组中,并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
-
public static void main(String[] args) { HashSet<String> set = new HashSet<>(); //添加元素 set.add("001"); set.add("hello"); set.add("java"); set.add(null); set.add("hello world"); /* * 迭代器 * 1. 获取迭代器 * 2. hasNext next */ Iterator<String> it = set.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); } } /* null 001 java hello hello world */
-
-
HashSet集合判断两个元素相等的标准:
- 两个元素通过hashCode方法比较相等,并且两个对象的equals方法返回的值也相等
-
HashSet的不可重复性 - 唯一
- Set中如何判断元素是否相同?
- 使用equals和hashCode两个方法进行判断
- 如果自定义类使用Set进行存储,要求元素不能重复应该如何做?
- 重写euqals和hashCode
- 如果没有重写equals和hashCode方法,元素可以重复添加
- 重写equals和hashCode方法以后,添加相同的元素只会添加一次
- Set中如何判断元素是否相同?
-
HashSet中元素添加的过程
- 当向HashSet中添加元素时,HashSet会调用该对象的hashCode()方法来计算该对象的hash值,然后根据得到的hash值,通过某种散列函数决定该对象在HashSet底层数组中存储的位置
- 如果此位置上没有其他元素,则添加成功
- 如果此位置上有其他元素,则进行进一步比较
- 比较两元素的hash的值
- 如果两个元素的hash值不相等,添加成功
- 如果两个元素的hash值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么添加该元素,如果该数组的位置已经有元素了,那么会通过链表的方式继续链接
- 当向HashSet中添加元素时,HashSet会调用该对象的hashCode()方法来计算该对象的hash值,然后根据得到的hash值,通过某种散列函数决定该对象在HashSet底层数组中存储的位置
-
HashSet底层结构
- 数组 + 链表
- 如果两个元素的hashCode值相等,但是他们的equals值不相等,那么他们将会以链表的形式存储在同一个hashCode地址下
11.6.5 LinkedHashSet使用
LinkedHashSet是
HashSet的子类
- LinkedHashSet 根据元素的
hashCode
值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。 - LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能
- LinkedHashSet不允许集合元素重复
public class MyTest03 {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("001");
set.add("hello");
set.add("java");
set.add(null);
set.add("hello world");
for (String s : set) {
System.out.println(s);
}
}
}
/*
001
hello
java
null
hello world
*/
11.6.6 TreeSet使用
-
TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
-
TreeSet底层使用红黑树结构存储数据。
-
TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
public class MyTest04 {
public static void main(String[] args) {
//TreeSet - 自然排序
TreeSet<String> set = new TreeSet<>();
set.add("A");
set.add("abc");
set.add("ab");
set.add("0001qqqqq");
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
TreeSet<String> set1 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
set1.add("A");
set1.add("abc");
set1.add("ab");
set1.add("0001qqqqq");
for (String s : set1) {
System.out.println(s);
}
}
}
/*
0001qqqqq
A
ab
abc
------------------------------
A
ab
abc
0001qqqqq
*/
11.7 Map接口
11.7.1 何为Map
- Map与Collection并列存在。用于保存具有映射关系的数据:key-value
- Map中的key和value都可以是任何引用类型的数据。
- Map中的key用Set来存放,不允许重复,key所对应的类,须重写hashCode()和equals()方法。常用String类作为Map的key。
- key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value
- Hashtable:古老的实现类,线程安全的,效率低,不能存储
null
的key
和value
。 - Properties:常用来处理配置文件。
key
和value
都是String
类型。
11.7.2 HashMap
HashMap是Map接口使用频率最高的实现类
- 允许使用 null键 和 null值 ,与HashSet一样,不保证映射的顺序
- 所有的key构成的集合是Set:无序的,不可重复的,所以,key所在的类要重写 equals() 和 hashCode() 方法
11.7.3 Map常见操作
-
public V put(K key, V value)
-
将指定的key - vaule添加到当前map对象中
-
如果添加的元素和其他元素的key相同,就会将value替换
-
Hash Map能添加null的key和value
-
//创建HashMap对象 HashMap<String, Object> map = new HashMap<>(); //添加元素 - k-v map.put("snum", "A100"); map.put("sname", "zs"); map.put("sage", 20); map.put("saddr", "qd"); //如果添加的元素和其他元素的key相同,就会讲value替换 map.put("saddr", "sh"); //HashMap能够添加null的key和value map.put(null, null); //遍历的顺序和添加的顺序是不一样的 System.out.println(map); ArrayList<String> hobby = new ArrayList<>(); hobby.add("爬山"); hobby.add("游泳"); hobby.add("阅读"); HashMap<String, Object> map1 = new HashMap<>(); map1.put("sgender", "m"); map1.put("shobby", hobby); System.out.println(map1); /* {null=null, snum=A100, sname=zs, saddr=sh, sage=20} {shobby=[爬山, 游泳, 阅读], sgender=m} */
-
-
public void putAll(Map m)
-
将m中的所有key - value 对存放到当前map中
-
//添加另一个map map.putAll(map1); System.out.println(map); /* {null=null, shobby=[爬山, 游泳, 阅读], snum=A100, sname=zs, saddr=sh, sgender=m, sage=20} */
-
-
public V remove(Object key)
-
移除指定key的 key - value 对,并返回value
-
//删除键值对 map.remove("shobby"); System.out.println(map); /* {null=null, snum=A100, sname=zs, saddr=sh, sgender=m, sage=20} */
-
-
public void clear()
-
清空当前Map中的所有数据
-
//清楚map所有的数据 map.clear(); System.out.println(map); //{}
-
-
public V get(Object key)
-
获取指定key对应的value
-
//根据key获取到对应的value System.out.println(map.get("sname")); System.out.println(map.get("sname111")); /* zs null */
-
-
public boolean containsKey(Object key)
- 判断是否包含指定的key
-
public boolean containsValue(Object value)
-
判断是否包含指定的value
-
//判断是否包含特定的key System.out.println(map.containsKey("sname")); //判断是否包含特定的value System.out.println(map.containsValue("zs")); /* true true */
-
-
public int size()
-
返回map中 key - value 对的个数
-
//获取键值对个数 System.out.println("键值对个数:" + map.size()); //键值对个数:6
-
-
public boolean isEmpty()
-
判断当前map是否为空
-
//是否为空 System.out.println("是否为空:" + map.isEmpty()); //是否为空:false
-
-
public boolean equals(Object o)
-
判断当前map和参数对象obj是否相等
-
//判断当前map和参数对象obj是否相等 System.out.println("是否相等:" + map.equals(map1)); //是否相等:false
-
-
public Set keySet()
-
获取所有的key
-
//获取所有的key System.out.println(map.keySet()); // [null, snum, sname, saddr, sgender, sage]
-
-
public Collection values()
-
返回map中 key - value 对的个数
-
//获取键值对个数 System.out.println("键值对个数:" + map.size()); //键值对个数:6
-
-
public boolean isEmpty()
- 判断当前map是否为空
-
public boolean equals(Object o)
-
判断当前map和参数对象obj是否相等
-
//是否为空 System.out.println("是否为空:" + map.isEmpty()); //判断当前map和参数对象obj是否相等 System.out.println("是否相等:" + map.equals(map1)); /* 是否为空:false 是否相等:false */
-
-
public Set keySet()
- 获取所有key构成的Set集合
-
public Collection values()
-
获取所有value构成的Collection集合
-
//获取所有的key Set<String> set = map.keySet(); System.out.println(set); //获取所有的value Collection<Object> values = map.values(); System.out.println(values); /* [null, snum, sname, saddr, sgender, sage] [null, A100, zs, sh, m, 20] */
-
-
public Set<Map.Entry<K,V>> entrySet()
-
判断当前map是否为空
-
//返回所有key - value对 构成的Set集合 Set<Map.Entry<String, Object>> entries = map.entrySet(); System.out.println(entries); //[null=null, snum=A100, sname=zs, saddr=sh, sgender=m, sage=20]
-
11.7.4 HashMap遍历
public static void main(String[] args) {
HashMap<String, Object> map = new HashMap<>();
//添加元素 - k-v
map.put("snum", "A100");
map.put("sname", "zs");
map.put("sage", 20);
map.put("saddr", "qd");
/*
* HashMap遍历 - 既要获取key,又要获取key对应的value
* 1. 获取所有的key,遍历key,通过key获取value
* */
//获取所有key
Set<String> keys = map.keySet();
//遍历key,获取key对应的value
for (String key : keys) {
Object value = map.get(key);
//System.out.println("key - " + key + "\tvalue - " + value);
System.out.println(key + ":" + value);
} System.out.println("++++++++++++++++++++++++++++++++");
//使用迭代器
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
Object value = map.get(key);
System.out.println(key + ":" + value);
}
System.out.println("--------------------------------");
//使用entrySet的增强for循环
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
String key = entry.getKey();
Object value = entry.getValue();
System.out.println(key + ":" + value);
} System.out.println("=================================");
Iterator<Map.Entry<String, Object>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey();
Object value = entry.getValue();
System.out.println(key + ":" + value);
} System.out.println("++++++++++++++++++++++++++++++++++");
map.forEach((k, v) -> System.out.println(k + ":" + v));
ArrayList<String> list = new ArrayList<>();
list.add("111111");
list.add("1111");
list.add("1111");
list.add("2342342323423");
//统计list有哪些长度值
HashSet<Integer> lens = new HashSet<>();
for (String s : list) {
lens.add(s.length());
}
System.out.println(lens);
List<Integer> lens1 = list.stream().map(item -> item.length()).distinct().collect(Collectors.toList());
System.out.println(lens1);
}
11.7.5 LinkedHashMap
- 元素包含指向前后元素的指针,能够记录添加元素的先后顺序
- 实际上就是元素添加的顺序与遍历的顺序是相同的
public static void main(String[] args) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
//添加元素 - k-v
map.put("snum", "A100");
map.put("sname", "zs");
map.put("sage", 20);
map.put("saddr", "qd");
Set<String> keys = map.keySet();
for (String key : keys) {
Object value = map.get(key);
System.out.println(key + ":" + value);
}
}
/*
snum:A100
sname:zs
sage:20
saddr:qd
*/
11.7.6 TreeMap
- 可按照添加key - value中key的指定属性进行排序,底层使用红黑树
public static void main(String[] args) {
//自然排序 - 根据key进行排序
TreeMap<String, Object> map = new TreeMap<>();
//添加元素 - k-v
map.put("snum", "A100");
map.put("sname", "zs");
map.put("sage", 20);
map.put("saddr", "qd");
Set<String> keys = map.keySet();
for (String key : keys) {
Object value = map.get(key);
System.out.println(key + ":" + value);
}
//定制排序 - 根据key的长度
TreeMap<String, Object> map1 = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
System.out.println("----------------------------------------");
//添加元素 - k-v
map1.put("snum", "A100");
map1.put("sname11111111111", "zs");
map1.put("sage111", 20);
map1.put("saddr11111111111111111111", "qd");
keys = map1.keySet();
for (String key : keys) {
Object value = map1.get(key);
System.out.println(key + ":" + value);
}
}
十二、异常
12.1 概述
- 异常: 异常就是代表程序出现的问题
- 误区:不是以后不出现异常,而是出现异常后如何处理
-
Error: Java虚拟机无法解决的严重问题,代表系统级别错误(属于严重问题)
- StackOverflowError
- OutOfMemoryError
-
Exception: 异常,代表程序可能出现的问题,因编程错误或偶然的外在因素导致的一般性问题,通常使用Exception以及他的子类来封装程序出现的问题
- ArithmeticException
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
-
运行时异常:RuntimeException及其子类,编译阶段不会出现异常,运行时出现的异常(数组索引越界异常)
- 编译器不要求强制处置的异常;
- 一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
- java.lang.RuntimeException 类及它的子类都是运行时异常;
- 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
-
编译时异常:编译阶段就会出现异常提醒的
- 编译器要求必须处置的异常;
- 除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于编译时异常;
- 编译器要求Java程序必须捕获或声明所有编译时异常;
- 对于这类异常,如果程序不处理,可能会带来意想不到的结果
-
运行时异常和编译时异常的区别?
- **编译时异常:**除了RuntimeException和它的子类,其它都是编译时异常,编译阶段需要进行处理,作用在于提醒程序员
- **运行时异常:**RuntimeException本身和所有子类,都是运行时异常,编译阶段不报错,是程序运行时出现的,一般是由于序参数传递错误带来的问题
-
异常的作用
-
查询bug的关键参考信息
-
作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况
-
12.2 异常的处理方式
-
JVM默认的处理方式
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
- 程序停止执行,下面的代码不会再执行
-
自己处理(捕获异常)- try - catch - finally
try{ //可能产生异常的代码 } catch(ExceptionName1 e ){ //当产生ExceptionName1型异常时的处置措施 } catch(ExceptionName2 e ){ //当产生ExceptionName2型异常时的处置措施 }finally{ //通常进行一些最终处理,无论是否发生异常,都无条件执行的语句 } //ctrl + alt + t
- public String getMessage()
- 返回此可抛出的简短描述
- public String toString()
- 返回此throwable的详细消息字符串
- public void printStackTrace()
- 在底层是利用System.err.println进行输出
- 把异常的错误信息输出在控制台
- 细节:只打印,不停止程序
public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; try { System.out.println(arr[10]); } catch (ArrayIndexOutOfBoundsException e) { String message = e.getMessage(); System.out.println(message); String str = e.toString(); System.out.println(str); e.printStackTrace(); } } /* 10 java.lang.ArrayIndexOutOfBoundsException: 10 java.lang.ArrayIndexOutOfBoundsException: 10 at com.test.test1.ExceptionDemo1.main(ExceptionDemo1.java:12) */
- 注意:
- 使用try-catch-finally处理异常 – “由程序员自己处理异常”
- try代码块包裹的可能出现异常的代码,出现异常之后就会抛出“异常类的对象”
- catch代码块用来对出现的异常进行处理,捕获try代码块抛出的异常类对象,这个异常类对象就会赋值给()中的引用
- catch代码块可以有多个,分别处理不同类型的异常,异常类型代表的是哪种类型,catch代码块就只能处理对应类型的异常
- 异常类型如果存在父类和子类的情况,子类一定要写在上面,父类要写在下面
- finally代码块不管是否出现异常都会被运行, finally代码块不是必须的
- public String getMessage()
-
抛出异常
-
throws
-
写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常
-
格式:
-
[修饰符] 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2 { 方法体; return 返回值; }
-
-
编译时异常:必须要写
-
运行时异常:可以不写
-
注意:
- throws在方法定义时使用,表示这个方法可能会出现异常
- throws后面可以写多个异常类型,表示该方法可能出现的异常或者子类的异常
- throws可以写异常类的父类
-
-
throw
-
写在方法内,结束方法
-
手动抛出异常对象,交给调用者,方法中下面的代码不再执行了
-
格式:
-
public void 方法() { throw new NullPointerException(); }
-
-
-
12.3 自定义异常
-
本质:自定义的java类
-
要求:继承Exception或Exception的子类
-
格式:
-
public class NameFormatException extends RuntimeException{ public NameFormatException() { } public NameFormatException(String message) { super(message); } } //throw new RuntimeException(name + "格式有误");
-
-
技巧:
- NameFormat:表示当前异常的名字,表示姓名格式化的问题
- Exception:表示当前类是一个异常类
- 运行时:RuntimeException 核心 表示由于参数错误而导致的问题
- 编译时:Exception 核心 提醒程序员检查本地信息
十三、IO
13.1 File类
File类被定义为文件和目录路径名的抽象表示形式,这是因为File类既可以表示文件也可以表示目录,他们都通过对应的路径来描述,这个路径既可以是存在的也可以是不存在的
- 通过构造函数创建一个 File类 对象,则该对象就是指定文件的引用,可以通过该对象对文件操作
File f = new File("C:/Users/17584/Desktop/FileDemo1.txt");
-
File三种构造方法
-
public File(String pathname) 把字符串表示的路径变成File对象 public File(String parent, String child) 把父级路径和子级路径进行拼接 public File(File parent, String child) 把父级路径和子级路径进行拼接
-
-
File类常见成员方法
-
public boolean isDirectory()
- 判断此路径名表示的File是否为文件夹
-
public boolean isFile()
- 判断此路径名表示的File是否为文件
-
public boolean exists
- 判断此路径名表示的File是否存在
-
public boolean isHidden()
- 判断此路径名表示的File是否为隐藏文件
-
public boolean canRead()
- 判断此路径名表示的File是否可读
-
public boolean canWrite()
- 判断此路径名表示的File是否可写
-
public boolean mkdir()
- 创建单极文件夹
-
public boolean mkdirs()
- 创建多级文件夹
-
public boolean createNewFile()
- 创建一个新的文件
-
public boolean delete()
- 删除此路径名表示的File,返回删除结果
-
public getName()
- 获取此路径名表示的File的名称
-
public long length()
- 返回文件的大小(字节数量)
-
public String getAbsolutePath()
- 返回文件的绝对路径
-
public String getPath()
- 返回定义文件时使用的路径
-
public long lastModified()
- 返回文件的最后修改时间(时间毫秒值)
-
public File[] listFiles()
- 返回目录下的所有文件和目录
-
public static void main(String[] args) {
/*
* File类 - 文件和目录的抽象表示形式,既可以代表文件也可以代表目录
* */
//创建File类的对象
/*
* 路径的写法 \\ /
* */
//File f1 = new File("C:\\Users\\Maxwell\\Desktop\\test\\testfile.txt");
File f1 = new File("C:/Users/17584/Desktop/FileDemo1.txt");
File f2 = new File("C:/Users/17584/Desktop");
File f3 = new File("C:/Users/17584/Desktop/FileDemo3.txt");
System.out.println("是否是隐藏文件:" + f1.isHidden());
System.out.println("是否可读:" + f1.canRead());
System.out.println("是否可写:" + f1.canWrite());
System.out.println("绝对路径:" + f1.getAbsolutePath());
System.out.println("获取文件名:" + f1.getName());
System.out.println("是否是目录:" + f1.isDirectory());
System.out.println("是否是文件:" + f1.isFile());
System.out.println("是否是目录:" + f2.isDirectory());
System.out.println("是否是文件:" + f2.isFile());
System.out.println("最后修改时间:" + f1.lastModified()); //时间戳
//时间戳 --> 特定格式的表示时间的字符串
Date date = new Date(f1.lastModified());
//Date --> 特定格式的表示时间的字符串
SimpleDateFormat sft = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str = sft.format(date);
System.out.println(str);
System.out.println("文件大小:" + f1.length()); //Byte
//判断目录是否存在
if (!f3.exists()) {
//创建目录
f3.mkdir();
}
f3.delete();
//遍历文件夹f2中的文件
File[] files = f2.listFiles(); //下一级
for (File file : files) {
System.out.println(file);
}
}
/*
是否是隐藏文件:false
是否可读:true
是否可写:true
绝对路径:C:\Users\17584\Desktop\FileDemo1.txt
获取文件名:FileDemo1.txt
是否是目录:false
是否是文件:true
是否是目录:true
是否是文件:false
最后修改时间:1706940841069
2024年02月03日 14:14:01
文件大小:0
C:\Users\17584\Desktop\db
C:\Users\17584\Desktop\desktop.ini
*/
- File遍历
public static void main(String[] args) {
File f = new File("C:/Users/17584/Desktop/test");
listAll(f);
System.out.println("----------------------------------------");
listAll1("|--", f);
}
/*
* 列出文件夹下的所有的文件和文件夹 ”子子孙孙“
* */
public static void listAll(File file) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
if(f.isDirectory()) { //判断是否是文件夹,是继续列出该文件夹下的所有文件和文件夹
listAll(f);
}
}
}
/*
* |--testfile.txt
* |--testfile1.txt
* |--a
* |--1.txt
* |--2.txt
* |--b
* */
public static void listAll1(String head, File file) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(head + f.getName());
if(f.isDirectory()) { //判断是否是文件夹,是继续列出该文件夹下的所有文件和文件夹
listAll1("\t" + head, f);
}
}
}
/*
C:\Users\17584\Desktop\test\a
C:\Users\17584\Desktop\test\a\aa
C:\Users\17584\Desktop\test\b
----------------------------------------
|--a
|--aa
|--b
*/
13.2 IO流
问题引入:
- File类只能对文件本身进行操作,不能读写文件里面存储的数据
- 所以想要对文件中的数据进行读写就需要IO流
13.2.1 何为IO流
I/O
是Input/Output
的缩写,I/O
技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
IO流
是一组有序的,有起点和终点的数据集合,是对数据传输的总称和抽象。
IO作用:
- 人机交互
- 文件数据读取写入,数据持久化保存
IO
流的源和目的地:
- 内存
- 控制台
- 磁盘文件
- 网络端点
关于Input
和Output
:
Input
读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中;Output
将程序(内存)数据输出到磁盘、光盘等存储设备中。
13.2.2 IO流分类
- 按照数据流流向不同
- 输入流
* - 输出流
- 输入流
- 按照处理的数据单元不同
-
字节流
- 操作的数据单元是8位字节
- InputStream、OutpurStream
- 二进制文件(声音、图片、视频),所有类型文件
-
字符流
- 操作的数据单元是16位字节
- Reader、Writer
- 处理纯文本文件
-
十七、 Java8的新特性
Java 8
是Java语言开发的一个主要版本。Java 8
是Oracle公司于2014年3月发布,可以看成是自Java 5
以来最具革命性的版本。Java 8
为Java
语言、编译器、类库、开发 工具与JVM
带来了大量新特性。
- 速度更快
- 代码更少(增加了新的语法:
Lambda
表达式)- 强大的
Stream API
- 便于并行
- 最大化减少空指针异常:
Optional
Nashorn
引擎,允许在JVM
上运行JS
应用
17.1Lambda表达式
-
Lambda表达式是JDK8开始后的一中新语法形式
-
作用:简化匿名内部类的代码写法
-
格式:
-
() -> {}
-
() :匿名内部类被重写方法的形参列表
-
{} :被重写方法的方法体代码
-
-> :语法形式,无实际含义
-
//语法 (参数列表) -> { Lambda体; } //匿名内部类 useInterA(new InterA() { @Override public void show() { System.out.println("匿名内部类重写后的方法"); } }); //Lambda useInterA(() -> { System.out.println("Lambda表达式重写后的方法"); });
-
-
限制:
- Lambda表达式,只允许操作函数式编程接口 : 有,且只有一个抽象方法的接口
-
规则:
- 如果参数列表没有参数,()不能省略,必须要写
- 如果参数列表只有一个参数,()可以省略
- 如果参数列表有多个参数,()不能省略
- 有参数的情况下,参数列表可以省略
- 如果Lambda体只有一条语句,{}可以省略,末尾的;也可以省略
- 如果Lambda体有多条语句,{}不可以省略
- 如果Lambda体只有一条语句,并且这条语句表示返回值(return 语句),return可以省略
- 在函数式接口上使用Lambda表达式
public static void main(String[] args) {
//使用匿名内部类创建线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("哈喽");
}
});
//启动线程
thread.start();
//对ArrayList中的元素进行排序
ArrayList<String> list = new ArrayList<>();
list.add("javaweb");
list.add("js");
list.add("hello");
list.add("java");
list.add("MySql");
Collections.sort(list, new Comparator<String >() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(list);
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
/*
分析:
1.上面的三段代码,真正起作用的是被重写的方法的方法体
2.Java中,方法的参数只能是基本数据类型或者引用数据类型
总结:
方法/行为无法作为方法参数进行传递 -- 为了编译通过就要写很多额外的代码
如何解决:
让行为作为方法的参数传递 -- 行为参数化 -- Lambda表达式
*/
Thread t1 = new Thread(() -> System.out.println("hello"));
t1.start();
Collections.sort(list, (s1, s2) -> -(s1.length() - s2.length()));
System.out.println(list);
list.forEach(s -> System.out.println(s));
}
f2.isDirectory());
System.out.println(“是否是文件:” + f2.isFile());
System.out.println(“最后修改时间:” + f1.lastModified()); //时间戳
//时间戳 --> 特定格式的表示时间的字符串
Date date = new Date(f1.lastModified());
//Date --> 特定格式的表示时间的字符串
SimpleDateFormat sft = new SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”);
String str = sft.format(date);
System.out.println(str);
System.out.println(“文件大小:” + f1.length()); //Byte
//判断目录是否存在
if (!f3.exists()) {
//创建目录
f3.mkdir();
}
f3.delete();
//遍历文件夹f2中的文件
File[] files = f2.listFiles(); //下一级
for (File file : files) {
System.out.println(file);
}
}
/*
是否是隐藏文件:false
是否可读:true
是否可写:true
绝对路径:C:\Users\17584\Desktop\FileDemo1.txt
获取文件名:FileDemo1.txt
是否是目录:false
是否是文件:true
是否是目录:true
是否是文件:false
最后修改时间:1706940841069
2024年02月03日 14:14:01
文件大小:0
C:\Users\17584\Desktop\db
C:\Users\17584\Desktop\desktop.ini
*/
* **File遍历**
```java
public static void main(String[] args) {
File f = new File("C:/Users/17584/Desktop/test");
listAll(f);
System.out.println("----------------------------------------");
listAll1("|--", f);
}
/*
* 列出文件夹下的所有的文件和文件夹 ”子子孙孙“
* */
public static void listAll(File file) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
if(f.isDirectory()) { //判断是否是文件夹,是继续列出该文件夹下的所有文件和文件夹
listAll(f);
}
}
}
/*
* |--testfile.txt
* |--testfile1.txt
* |--a
* |--1.txt
* |--2.txt
* |--b
* */
public static void listAll1(String head, File file) {
File[] files = file.listFiles();
for (File f : files) {
System.out.println(head + f.getName());
if(f.isDirectory()) { //判断是否是文件夹,是继续列出该文件夹下的所有文件和文件夹
listAll1("\t" + head, f);
}
}
}
/*
C:\Users\17584\Desktop\test\a
C:\Users\17584\Desktop\test\a\aa
C:\Users\17584\Desktop\test\b
----------------------------------------
|--a
|--aa
|--b
*/
13.2 IO流
问题引入:
- File类只能对文件本身进行操作,不能读写文件里面存储的数据
- 所以想要对文件中的数据进行读写就需要IO流
13.2.1 何为IO流
I/O
是Input/Output
的缩写,I/O
技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
IO流
是一组有序的,有起点和终点的数据集合,是对数据传输的总称和抽象。
IO作用:
- 人机交互
- 文件数据读取写入,数据持久化保存
IO
流的源和目的地:
- 内存
- 控制台
- 磁盘文件
- 网络端点
关于Input
和Output
:
Input
读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中;Output
将程序(内存)数据输出到磁盘、光盘等存储设备中。
13.2.2 IO流分类
- 按照数据流流向不同
- 输入流
* - 输出流
- 输入流
- 按照处理的数据单元不同
-
字节流
- 操作的数据单元是8位字节
- InputStream、OutpurStream
- 二进制文件(声音、图片、视频),所有类型文件
-
字符流
- 操作的数据单元是16位字节
- Reader、Writer
- 处理纯文本文件
-
十七、 Java8的新特性
Java 8
是Java语言开发的一个主要版本。Java 8
是Oracle公司于2014年3月发布,可以看成是自Java 5
以来最具革命性的版本。Java 8
为Java
语言、编译器、类库、开发 工具与JVM
带来了大量新特性。
- 速度更快
- 代码更少(增加了新的语法:
Lambda
表达式)- 强大的
Stream API
- 便于并行
- 最大化减少空指针异常:
Optional
Nashorn
引擎,允许在JVM
上运行JS
应用
17.1Lambda表达式
-
Lambda表达式是JDK8开始后的一中新语法形式
-
作用:简化匿名内部类的代码写法
-
格式:
-
() -> {}
-
() :匿名内部类被重写方法的形参列表
-
{} :被重写方法的方法体代码
-
-> :语法形式,无实际含义
-
//语法 (参数列表) -> { Lambda体; } //匿名内部类 useInterA(new InterA() { @Override public void show() { System.out.println("匿名内部类重写后的方法"); } }); //Lambda useInterA(() -> { System.out.println("Lambda表达式重写后的方法"); });
-
-
限制:
- Lambda表达式,只允许操作函数式编程接口 : 有,且只有一个抽象方法的接口
-
规则:
- 如果参数列表没有参数,()不能省略,必须要写
- 如果参数列表只有一个参数,()可以省略
- 如果参数列表有多个参数,()不能省略
- 有参数的情况下,参数列表可以省略
- 如果Lambda体只有一条语句,{}可以省略,末尾的;也可以省略
- 如果Lambda体有多条语句,{}不可以省略
- 如果Lambda体只有一条语句,并且这条语句表示返回值(return 语句),return可以省略
- 在函数式接口上使用Lambda表达式
public static void main(String[] args) {
//使用匿名内部类创建线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("哈喽");
}
});
//启动线程
thread.start();
//对ArrayList中的元素进行排序
ArrayList<String> list = new ArrayList<>();
list.add("javaweb");
list.add("js");
list.add("hello");
list.add("java");
list.add("MySql");
Collections.sort(list, new Comparator<String >() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(list);
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
/*
分析:
1.上面的三段代码,真正起作用的是被重写的方法的方法体
2.Java中,方法的参数只能是基本数据类型或者引用数据类型
总结:
方法/行为无法作为方法参数进行传递 -- 为了编译通过就要写很多额外的代码
如何解决:
让行为作为方法的参数传递 -- 行为参数化 -- Lambda表达式
*/
Thread t1 = new Thread(() -> System.out.println("hello"));
t1.start();
Collections.sort(list, (s1, s2) -> -(s1.length() - s2.length()));
System.out.println(list);
list.forEach(s -> System.out.println(s));
}