JavaSE
第一部分:Java基础编程
一、变量与运算符
1.1 Java中标识符的命名规范:
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
1.2 变量
变量的使用:
1、Java定义变量的格式:数据类型 变量名 = 变量值;
int myAge = 12;
System.out.println(myAge);
2、说明:
① 变量必须先声明在使用
② 变量都定义在其作用域内。在作用域内,他是有效的,出了作用域就失效了
③ 同一个作用域内,不可以声明两个同名的变量
变量的分类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKE3KJUG-1649144014070)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210720224625165.png)]
1.3 基本数据类型之间的运算规则:
前提:这里讨论的只是七种变量间的运算,不包含布尔类型。
1.自动类型提升:
当容量小的数据类型变量与数据容量大的数据类型变量做运算时,结果自动提升为容量大的数据类型。
特别的:当byte、char、short三种类型的变量做运算时,结果为int型。
2.强制类型转换:自动提升类型运算的逆运算
① 需要使用强转符:()
double d1 = 12.9;
int i1 = (int)d1;
System.out.println(i1);
② 注意点:强制类型转换,可能导致精度损失。
说明:此时的容量大小指的是,表示数的范围和大小。
1.4 计算机中不同的进制的使用说明
对于整数,有四种表达方式:
① 二进制:0,1,满2进1,以0b或0B开头。
② 十进制:0 - 9,满10进1.
③ 八进制:0 - 7,满8进1,以数字0开头。
④ 十六进制:0 - 9以及A - F,满16进1,以数字0x或0X开头表示。此处的A - F不区分大小写。
十进制和二进制之间的转换:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1O3tSL2o-1649144014071)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210721115722311.png)]
1.5 (前)++和(后)++
① (前)++:先自增1,后运算
② (后)++:先运算,后自增1
public class day03Test {
public static void main(String[] args) {
boolean x = true;
boolean y = false;
short z = 40;
if ((z++ == 40) && (y = true)) {
z++;
}
if ((x = false) || (++z == 43)) {
z++;
}
System.out.println("z = " + z); // 44
}
}
1.6 连接符
任何数据类型和String类型使用连接符都会变为String类型
1.7 比较运算符
【典型代码】
public class BiJiaoTest {
public static void main(String[] args) {
int i = 12;
int j = 5;
System.out.println(i == j);// false
System.out.println(i = j);// 5
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2); //false
System.out.println(b1 = b2);// false
}
}
【特别说明的】
1.比较运算符的结果是boolean类型
2.> < >= <= :只能使用在数值类型的数据之间。
3.== : 不仅可以使用在数值类型数据之间,也可以使用在其他引用类型变量之间。
Account acct1 = new Account(1000);
Account acct2 = new Account(2000);
boolean b1 = (acct1 == acct2); // 比较两个Account是否是同一个账户
boolean b2 = (acct1 != acct2);
1.8 逻辑运算符
& && | || ! ^
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcJHBGKO-1649144014072)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210724111044271.png)]
二、流程控制
2.1 顺序结构:程序从上到下执行
2.2 分支结构:
2.2.1 if-else
结构一:
if(条件表达式){
执行表达式1
}
结构二:
if(){
执行表达式1
}else{
执行表达式2
}
结构三:
if(条件表达式){
执行表达式1
}else if(){
执行表达式2
}else if{
执行表达式3
........
}else{
执行表达式n
}
说明:
1.else 结构是可选的
2.针对于条件表达式:
>如果多个条件表达式之间是"互斥"关系(或者没有交集的关系),哪个判断和执行语句声明在上面还是下面,无所谓。
>如果多个条件表达式之间有交集的关系,需要根据实际情况考虑清楚,应该将那个结构声明在上面。
>如果多个条件表达式之间有包含关系,需要将范围小的声明在范围大的上面。否则,小的就没机会执行了。
2.2.2 switch-case
格式:
int num = 2;
switch (num) {
case 0:
System.out.println("zero");
break;
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
break;
case 3:
System.out.println("three");
break;
default:
System.out.println("other");
}
说明:
① 根据switch表达式中的值,依次匹配各个case中的常量。一旦匹配成功,则进入相应的case结构中,调用其执行语句。
当调用完执行语句后,则仍然向下执行其他case结构中的执行语句,直到遇到break关键字或此switch-case结构末尾结束为止。
② break可以使用在switch-case结构中,表示一旦执行到此关键字,就跳出switch-case结构
③ switch结构中的表达式,只能是如下六种类型之一:byte、short、char、int、枚举类型、String类型
④ case之后只能声明常量,不能声明范围。
⑤ break关键字是可选的
⑥ default:也是可选的
2.2.3 Scanner类的使用
如何从键盘获取不同类型的变量,需要使用Scanner类
具体步骤:
1.导包:import java.util.Scanner;
2.Scanner的实例化
3.调用Scanner类的相关方法,来获取指定类型的变量
注意:需要根据相应的方法,来输入指定的类型的值。如果输入的数据与要求的类型不匹配时,会报异常:InputMisMatchException导致程序终止。
package com.atscitc.Lanqiao.javase;
// 1.导包:import java.util.Scanner;
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args) {
// 2.Scanner的实例化
Scanner sc = new Scanner(System.in);
// 3.调用Scanner类的相关方法,来获取指定类型的变量
System.out.println("请输入你的姓名:");
String name = sc.next();
System.out.println(name);
System.out.println("请输入你的年龄:");
int age = sc.nextInt();
System.out.println(age);
System.out.println("请输入你的体重:");
double weight = sc.nextDouble();
System.out.println(weight);
System.out.println("你是否相中了我??");
boolean isLove = sc.nextBoolean();
System.out.println(isLove);
}
}
2.3 循环结构
2.3.1 循环结构四要素
For循环结构的使用
一、循环结构的4个要素
① 初始化条件
② 循环条件 —>>是Boolean类型的
③ 循环体
④ 迭代条件
说明:通常情况下,循环结束都是因为②中的循环条件返回false了。
2.3.2 三种循环结构
for
for(①;②;④){
③;
}
// 执行过程:① -> ② -> ③ -> ④ ->②.....
while
①
while(②){
③;
④;
}
// 执行过程:① --> ② --> ③ --> ④ --> ② --> ③ --> ④......直到迭代条件不满足退出循环
说明:1.写while循环千万小心不要丢了迭代条件。一旦丢了,就可能导致死循环!
2.写程序要避免陷入死循环!
3.for循环和while循环是可以相互转换的!
for和while循环总结:
-
开发中,基本上从for和while循环中选择,实现循环结构。
-
for循环和while循环是可以相互转换的!
区别:for循环和while循环的初始化条件部分的作用范围不同。
-
写程序,避免出现死循环
do-while
do-while循环的结构:
①
do{
③;
④;
}while(②)
// 执行过程:① --> ③ --> ④ --> ②.....
说明:1.do-while循环至少会执行一次循环体
2.在开发中,使用for和while更多一些。
2.3.3无限循环和嵌套循环
1、“无限循环”结构:while(true)或for(;😉
总结:如何结束一个一个循环结构?
①方式一:当循环条件是false时
②方式二:在循环体中,执行break
2、嵌套循环
嵌套循环:将一个循环结构A声明在另一个循环结构B的循环体中,构成嵌套循环
外层循环:循环结构B
内层循环:循环结构A
3.说明
① 内层循环结构遍历一遍,只相当于外层循环循环体执行了一次
② 假设外层循环需要执行m次,内层循环需要执行n次。此时内存循环的循环体一共执行了m * n次
③ 内层循环执行完了跳出循环,下一次循环从头开始!!
④ 外层循环控制行数,内层循环控制列数
【典型练习】
public class NineNineTable {
public static void main(String[] args) {
/*
循环嵌套的应用1:
九九乘法表
1 * 1 = 1
2 * 1 = 2 2 * 2 = 4
.。。
9 * 1 = 9。。。9 * 9 = 81
*/
for (int i = 1; i <= 99; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(j + " * " + i + "=" + (i * j) + " ");
}
System.out.println();
}
}
}
补充:衡量一个功能代码的优劣:
1.正确性
2.可读性
3.健壮性
4.高效率与低存储:时间复杂度、空间复杂度(衡量算法的好坏)
关键字:break和continue
break和continue关键字的使用
适用范围 | 循环中使用的作用 | 相同点 | |
---|---|---|---|
break: | switch-case、循环结构中 | 结束当前循环 | 关键字后面不能声明执行语句 |
continue: | 循环结构中 | 结束当次循环 | 关键字后面不能声明执行语句 |
补充:带标签的break和continue的使用:
System.out.println("***************结束指定标识的一层循环结构***********************");
System.out.println("\n");
lable:for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 10; j++) {
if (j % 4 == 0) {
//break lable; // 结束指定标识的一层循环结构
continue lable; //结束指定标识的一层循环结构的当次循环
}
System.out.print(j);
}
System.out.println();
}
三、项目一
package com.atscitc.Lanqiao.javase;
import java.util.Scanner;
public class Utility {
private static Scanner scanner = new Scanner(System.in);
/**
* 用于界面菜单的选择。该方法读取键盘,如果用户键入’1’-’4’中的任意字符,则方法返回。返回值为用户键入字符。
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1);
c = str.charAt(0);
if (c != '1' && c != '2' && c != '3' && c != '4') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
/**
* 用于收入和支出金额的输入。该方法从键盘读取一个不超过4位长度的整数,并将其作为方法的返回值。
*/
public static int readNumber() {
int n;
for (; ; ) {
String str = readKeyBoard(4);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 用于收入和支出说明的输入。该方法从键盘读取一个不超过8位长度的字符串,并将其作为方法的返回值。
*/
public static String readString() {
String str = readKeyBoard(8);
return str;
}
/**
* 用于确认选择的输入。该方法从键盘读取‘Y’或’N’,并将其作为方法的返回值。
*/
public static char readConfirmSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
private static String readKeyBoard(int limit) {
String line = "";
while (scanner.hasNext()) {
line = scanner.nextLine();
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
package com.atscitc.Lanqiao.javase;
public class FamilyAccount {
public static void main(String[] args) {
boolean isFlag = true;
int balance = 10000;
String details = "收支\t\t账户金额\t\t收支金额\t\t说 明\n";
while (isFlag) {
System.out.println("----------------家庭收支记账软件--------------\n");
System.out.println(" 1收支明细");
System.out.println(" 2登记收入");
System.out.println(" 3登记支出");
System.out.println(" 4退出\n");
System.out.print(" 请选择(1 - 4):");
char selection = Utility.readMenuSelection();
switch (selection) {
case '1':
//System.out.println("收支明细");
System.out.println("---------------当前收支明细--------------");
System.out.println(details);
System.out.println("---------------------------------------\n");
break;
case '2':
// System.out.println("登记收入");
System.out.println("本次收入金额:");
int money = Utility.readNumber();
System.out.println("本次收入说明:");
String info = Utility.readString();
balance += money;
details += "收入" + "\t\t" + balance + "\t\t" + money + "\t\t\t" + info + "\n";
// System.out.println("收入" + "\t" + balance + "\t" + money + "\t" + info + "\n");
System.out.println("-------------------登记完成------------------");
break;
case '3':
// System.out.println("登记支出");
System.out.println("本次支出金额:");
money = Utility.readNumber();
System.out.println("本次支出说明:");
info = Utility.readString();
if (balance >= money) {
balance -= money;
details += "支出" + "\t\t" + balance + "\t\t" + money + "\t\t\t" + info + "\n";
} else {
System.out.println("余额不足,支付失败!");
}
System.out.println("------------------登记完成-------------------");
break;
case '4':
// System.out.println("退 出");
System.out.println("确认是否退出<Y/N>:");
char isExit = Utility.readConfirmSelection();
if (isExit == 'Y') {
isFlag = false;
}
break;
}
}
}
}
四、数组
4.1 数组的概述
- 1.数组的理解:数组(Array),是多个相同类型数据按一定顺序排列的集合,
-
并使用一个名字命名,并通过编号的方式对这些数据进行统一管理
- 2.数组相关概念:
-
数组名
-
元素
-
下标(或索引)
-
数组的长度:元素的个数
- 3.数组的特点:
- 1)数组是有序排列的
- 2)数组属于引用类型的变量。数组的元素既可以是基本数据类型,也可以是引用数据类型
- 3)创建数组对象会在内存中开辟一整块连续的空间
- 4)数组的长度一旦确定,就不能修改
- 4.数组的分类:
- ① 按照维数:一维,二维…
- ② 按照数组元素类型,基本数据类型元素的数组,引用元素类型元素的数组
、4.2 一维数组
4.2.1 一维数组的声明与初始化
1、一维数组的声明和初始化
//① 一维数组的声明和初始化 ----->> 静态初始化:数组的初始化和数组元素的赋值操作同时进行
int[] ids = new int[] {1001,1002,1003,1004};
//② 一维数组的声明和初始化 ----->> 动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[4];
//总结:一旦初始化完成,数组的长度也就确定了。
4.2.2 一维数组的元素的引用
2、如何调用数组指定位置的元素:通过下标的方式调用。
数组的下标是从0开始的,到数组的长度-1结束。
4.2.3 如何获取数组的长度
属性:length
4.2.4 如何遍历数组
for(int i = 0 ; i < names.length ; i ++) {
System.out.println(names[i]);
}
4.2.5 一维数组元素的默认初始化值
5、数组元素的默认初始化值
int[] num = new int[5];
System.out.println(num[0]);
short[] num1 = new short[5];
for(int i = 0 ; i < num1.length ; i ++) {
System.out.println("num1:" + num1[i]);//num1:0
}
float[] num2 = new float[5];
for(int i = 0 ; i < num2.length ; i ++) {
System.out.println("num2:" + num2[i]);//num2:0.0
}
double[] num3 = new double[5];
for(int i = 0 ; i < num3.length ; i ++) {
System.out.println("num3:" + num3[i]);//num3:0.0
}
char[] num4 = new char[5];
for(int i = 0 ; i < num4.length ; i ++) {
System.out.println("num4:" + num4[i] + "num4");//num4: num4
}
String[] num5 = new String[5];
for(int i = 0 ; i < num5.length ; i ++) {
System.out.println("num5:" + num5[i]);//num5:null
}
boolean[] num6 = new boolean[5];
for(int i = 0 ; i < num6.length ; i ++) {
System.out.println("num6:" + num6[i]);//num6:false
}
4.2.6 一维数组的内存解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRUhsltZ-1649144014073)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210727075518518.png)]
4.3 二维数组
4.3.1 关于二维数组的理解
1.理解
- 对于二维数组的理解,我们可以看成是一维数组array1又作为;另一个一维数组array2的元素而存在。
- 其实从数组底层运行机制来看,其实没有多维数组。
4.3.2 二维数组的声明和初始化
// 一维数组静态初始化
int[] arry = new int[] { 11, 22, 33, 44 };
// 二维数组静态初始化
int[][] arry1 = new int[][] { { 11, 22 }, { 33, 44 }, { 55, 66 } };
// 一维数组动态初始化
int[] arry2 = new int[5];
// 二维数组动态初始化
int[][] arry3 = new int[5][3];
4.3.3 如何调用数组指定位置的元素
System.out.println(arry[0]);// 11--->>一维数组指定位置元素的调用
System.out.println(arry1[0][1]);// 22--->>二维数组指定位置元素的调用
// System.out.println(arry1[0]);//[I@7852e922
// System.out.println(arry1[1]);//[I@7852e922
// System.out.println(arry2[0]); 0
// System.out.println(arry3[0][1]); 0
int[][] arry4 = new int[5][];
// System.out.println(arry4[0][1]); NullPointerException:空指异常
// 相当于在堆内存中,给arry5数组的外层的第1个元素,
// new了一个存储空间
int[][] arry5 = new int[5][];
arry5[0] = new int[3];
// System.out.println(arry5[1]);//null
// System.out.println(arry5[1][0]);//NullPointerException:空指异常
System.out.println(arry5[0][1]);// 0
4.3.4 如何获取数组的长度
System.out.println(arry[0]);// 11--->>一维数组指定位置元素的调用
System.out.println(arry1[0][1]);// 22--->>二维数组指定位置元素的调用
4.3.5 如何获取数组的长度
System.out.println(arry5.length);// 5
System.out.println(arry1[0].length);// 2
4.3.6 如何遍历数组
for (int i = 0; i < arry1.length; i++) {
for (int j = 0; j < arry1[i].length; j++) {
System.out.print(arry1[i][j] + " ");//11 22
} //33 44
System.out.println(); //55 66
}
4.3.7 数组元素的默认初始化值
- 二维数组的使用:
- 规定:二维数组分为外层数组的元素,内层数组的元素
- int[][] arr = new int[4][3];
- 外层元素:arr[0]arr[1]等
- 内层元素:arr[0][0],arr[1][2]等
- ⑤ 数组元素的默认初始化值
- 针对于初始化方式一:比如int[][] arr = new int[4][3];
-
外层元素的初始化值为:地址值
-
内层元素的初始化值为:与一维数组初始化情况相同
- 针对于初始化方式二:
-
比如:int[][] arr = new int[4][];
-
外层元素的初始化值为:null
-
内层元素的初始化值为:不能调用,报空指针异常。
4.3.8 二维数组的内存解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFBe6YJV-1649144014073)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210727124941527.png)]
4.4 数组常见算法
4.4.1 冒泡排序
int[] arr = new int[]{11,54,0,-78,32,1};
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
4.4.2 数组的反转
int[] arr = new int[]{11,54,0,-78,32,1};
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; i < arr.length; i++){
System.out.println(arr[i]);
}
4.4.3 线性查找
// 数组的查找 str1 = new String[] { “AA”, “BB”, “MM”, “GG” };
// 线性查找:MM
System.out.println();
boolean flag = false;
for (int i = 0; i < str1.length; i++) {
if (str1[i].equals("MM")) {
flag = true;
System.out.print("找到了,坐标位于:" + i);
break;
}
}
if (flag == false) {
System.out.println("很遗憾,没有找到!");
}
}
4.4.4 Arrays工具类的使用
① 定义在util包下
② 提供了很多操作数组的方法
public class ArraysTest {
public static void main(String[] args) {
// 1.boolean equals(inta,intb):判断两个数组是否相等
int[] arr1 = new int[] { 11, 22, 33, 44 };
int[] arr2 = new int[] { 11, 33, 22, 44 };
boolean isEquals = Arrays.equals(arr1, arr2);
System.out.println(isEquals);
// 2.String toString(inta):输出数组信息
String isArr = Arrays.toString(arr1);
System.out.println(isArr);
// 3.vTd fill(inta,int val):将指定值填充到数组之中
Arrays.fill(arr1, 10);
System.out.println(Arrays.toString(arr1));
// 4.void sort(inta):对数组进行排序
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2));
// 5.int binarySearch(inta,int key):对排序后的数组进行二分法检索指定的值
int index = Arrays.binarySearch(arr2, 33);
System.out.println(index);
}
}
五、面向对象-上
5.1 类和对象
1 、面向对象学习的三条主线
1.Java类及类的成员:属性、方法、构造器;代码块、内部类
2.面向对象的三大特征:封装、继承、多态、(抽象)
3.其他关键字:this、super、static、final、abstract、interface、 package、import等
”大处着眼,小处着手“
2 、面向对象与面向过程(理解)
1.面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做
2.面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
举例对比:”把大象装进冰箱“
3 、完成一个项目(或功能)的思路
➢根据问题需要,选择问题所针对的现实世界中的实体。
➢从实体中寻找解决问题相关的属性和功能,这些属性和功能就形 成了概念世界中的类。
➢把抽象的实体用计算机语言进行描述,形成计算机计界中类的定 义。即借助某种程序语言,把类构造成计算机能够识别和处理 的数据结构。
➢将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
4、 面向对象中两个重要的概念
类:对一类事物的描述,是抽象的、概念上的定义
对象:是实际存在的该类事物的每个个体,因而也称为实例
>面向对象程序设计的重点是类的设计
>设计类,就是设计类的成员
二者关系:对象,是由类new出来的,派生出来的
5、面向对象思路落地的实现规则
1.创建类,设计类主要成员
2.创建类的对象
3.通过”对象.属性“或”对象.方法“调用对象的结构
6、对象的创建和对象的内存解析
典型代码:
Person p1 = new Person();
Person p2 = new Person();
Person p3 = p1;//没有新创建一个对象,共用一个堆空间中的对象实体
说明:如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
内存解析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJ1ah6Hs-1649144014074)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210730083610840.png)]
7、匿名对象的使用
-
1.理解:我们创建的一个对象,没有显示的赋值给一个变量名。即为匿名对象
-
2.特征:匿名对象只能调用一次
典型代码:
// 匿名对象 // double Price = new Phone().price = 3580; // System.out.println("手机的价格为:" + Price); new Phone().playGame(); new Phone().sendEmail(); new Phone().price = 1999; new Phone().showPrice();//0.0 }
5.2 类的结构之一:属性
对比:属性 VS 局部变量
1、相同点:
-
1.1 定义变量的格式:数据类型 变量名 = 变量值
-
1.2 先声明,后使用
-
1.3 变量都有其对应的作用域
- 2、不同点:
-
2.1 在类中声明的位置不同
-
属性:直接定义在类的一对{ }内
-
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部变量
-
2.2 关于权限修饰符的不同
-
属性:可以在声明属性时,指明其权限,使用权限修饰符
-
常见的权限修饰符:public、private、protected、缺省 --->> 封装性
-
局部变量:不可以使用权限修饰符
-
2.3 默认初始化值的情况
-
属性:类的属性。根据其类型,都有初始化值。
-
整型:(byte、short、int、long):0
-
浮点型:(float、double):0.0
-
字符型:(char):0
-
布尔型:(boolean):false
-
引用数据类型:(类、数组、接口):null
-
-
局部变量:没有默认初始化值
-
意味着,我们在调用局部变量之前,一定要显示赋值
-
特别地,形参在调用时,我们赋值即可
-
2.4 在内存中加载的位置不同:
-
属性:加载到堆空间中(非static)
-
局部变量:加载到堆空间中
类的结构之二:方法
/*
-
类中方法的声明和使用
-
方法:描述类具有的功能
-
比如:Math类
-
1.举例
-
public void eat() { } public void sleep(int hour) { } public String getName(String name){ }
-
2.方法的声明
-
权限修饰符 返回值类型 方法名(形参列表){
-
方法体;
-
}
-
3.说明
-
3.1返回值类型:有返回值 VS 没有返回值
-
3.1.1 如果有返回值,则必须在方法声明时,指定返回值类型。
-
同时,方法中需要使用return关键字返回指定类型的变量或常量:return 数据;
-
如果方法没有返回值,则方法声明时,使用void来表示。通常不使用
-
return,如果写了return,则作为结束方法来使用:return;
-
3.2方法名:属于标识符,遵循标识符的规则和规范,“见名知意”
-
3.3形参列表:方法可以声明0个,1个,或者多个形参
-
格式:数据类型1 参数1,数据类型2 参数2…
-
3.4方法体:方法功能的实现
-
4.return关键字的使用:
-
1.使用范围:使用在方法体中
-
2.作用:① 结束方法
-
② 针对有返回值的方法,使用“return 数据”返回所需要的数据
-
3.注意点:return关键字后,不能声明执行语句
-
5.方法使用中,可以调用当前类的属性和方法
-
特殊地:方法A中,又调用了方法A,称为递归方法
6.对象数组的内存解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySW2W65q-1649144014075)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210731164446040.png)]
5.3 方法的重载
方法的重载(OverLoad)
*
-
1.在同一个类中,允许存在一个及以上的同名方法,只要他们的参数个数及参数类型不同即可。
-
"两同一不同":同一个类,同一个方法名
-
参数列表不同:参数个数不同、参数类型不同
-
2.判断是否是重载
-
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系
-
3.在通过对象调用方法时,如果确定某一个指定的方法:
-
方法名----->>参数列表
举例代码
public class OverLoad {
// 参数类型不同的重载
public void getSum(int i, int j) {
System.out.println(i + j);
}
public void getSum(double i, double j) {
System.out.println(i + j);
}
public void getSum(String j, int i) {
System.out.println(i + j);
}
public void getSum(int i, String j) {
System.out.println(i + j);
}
// 参数个数不同的重载
public void proDuct(int i, int j, int k) {
System.out.println(i * j * k);
}
public void proDuct(int i, int j) {
System.out.println(i * j);
}
}
5.4 可变个数形参的方法
1、jdk5.0新增的内容
-
2.具体使用:
-
2.1 可变个数形参的格式:数据类型 … 变量名
-
2.2 当调用可变形参个数的方法时,传入的参数可以是:0个、1个、2个…
-
2.3 可变个数形参的方法,与本类中方法名相同,形参不同的方法之间,构成重载
-
2.4 可变个数形参的方法,与本类中方法名相同,形参类型也相同的方法之间不构成重载
-
2.5 可变个数形参在方法的形参中,必须声明在末尾 —> public void show(int i,String … stus) {}
-
2.6 可变个数形参在方法的形参中,最多只能声明一个可变形参
举例代码:
public void show(String ... s) { for(int i = 0; i < s.length; i++) { System.out.println(s[i]);//Hello //World } }
2、 Java的值传递机制
1.针对于方法内变量的赋值举例
public class ValueTransferTest { public static void main(String[] args) { System.out.println("*******基本数据类型*******"); int i = 10; int m = i; System.out.println("i = " + i + ", m = " + m); m = 20; System.out.println("i = " + i + ", m = " + m);// i = 10;m = 20 System.out.println("***********引用数据类型*************"); Order o1 = new Order(); o1.i = 10; // o1.m = i; // o1.m = 20; // o1.Order1(); Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向堆空间中同一个对象实体。 //(图解见C:\developmentNotes\Study\Java\java内存解析图\理解引用数据类型变量赋值.png) System.out.println("o1 = " + o1.i + ", o2 = " + o2.i);//o1 = 10, o2 = 10 o2.i = 20; System.out.println("o1 = " + o1.i + ", o2 = " + o2.i);//o1 = 20, o2 = 20 } } class Order { int i; public void Order1() { // System.out.println("i = " + i + ", m = " + m); } }
规则:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值
如果变量是引用数据类型,此时赋值的变量所保存的数据是地址值
2.针对于方法的参数概念
形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的数据
3.Java中参数传递机制:值传递
规则: 如果参数是基本数据类型,此时实参赋给形参的是,实参真实存储的数据值。
如果参数是引用数据类型,此时实参赋给形参的是,实参存储数据的地址值。
5.5 面向对象的特征之一封装与隐藏
1、为什么要引入封装性?
① 我们程序设计追求“高内聚,低耦合”。 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉; 低耦合 :仅对外暴露少量的方法用于使用
② 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提 高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露 的暴露出来。这就是封装性的设计思想
2、问题引入
- 我们想使用户不能通过"对象.属性"的对属性进行赋值操作:使用private,
- 让用户只能通过方法调用属性赋值
- ---->>此时,针对于属性,就体现了封装性
3、封装性思想具体的代码体现:
体现一:类的属性XXX私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
体现二:不对外暴露的私有的方法
体现三:单例模式(将构造器私有化)
体现四:如果不希望包在包外被调用,可以将类设置为缺省
4、Java规定的4种权限修饰符
4.1 权限从小到大的顺序为:
4.2 具体的修饰范围:private < 缺省 < protected < public
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pFexQCmF-1649144014075)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210801201713168.png)]
4.3 权限修饰符可用来修饰的结构说明:
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
具体的修饰类的话,只能使用:缺省、public
构造器
1、作用
* 创建对象
* 初始化对象信息
2、说明
1.如果没有显示的定义类的构造器的话,则系统默认提供一个空参构造器
2.创建构造器的格式:
权限修饰符 类名(参数列表){
}
3.一个类中定义多个构造器,构成重载
4.一旦我们显示的定义了类的构造器,系统就不再提供默认的空参构造器
5.一个类中至少会有一个构造器
3、举例
class Person {
// 属性
String name;
int age;
// 构造器
public Person() {
System.out.println("Person...");
}
public Person(String n) {
name = n;
}
// 方法
public void eat() {
System.out.println("人吃饭");
}
public void sleep() {
System.out.println("人睡觉");
}
}
总结:属性赋值的先后顺序
- ① 默认初始化
- ② 显示赋值
- ③ 构造器中赋值
- ④ 通过"对象.属性"或"对象.方法"的方式,赋值
- 顺序:①–> ②–> ③–> ④
5.6 this关键字、package关键字和import关键字
this关键字
1、可以调用的结构
属性、方法;构造器
2、可以调用的属性、方法
this理解为:当前对象
在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。
特殊情况下,如果方法的形参和类的属性同名时,我们必须显示的使用"this.变量"的方式,表明此变量是属性,而非形参。
3、可以调用的构造器
① 我们在类的构造器中,可以显示的使用"this.(形参列表)"的方式,调用本类中指定的其他构造器
② 构造器不能通过"this.(形参列表)"的方式,调用自己
③ 如果一个类中有n个构造器,则最多有(n - 1)个构造器使用了"this.(形参列表)"
④ 规定:"this.(形参列表)"必须声明在当前构造器的首行 -->> public Person(int age) {
this();//位于首行
this.age = age;
}
⑤ 构造器内部,最多只能声明一个"this.(形参列表)",来调用其他构造器
package关键字
1、使用说明
1.为了更好的实现对类的管理,提供了包的概念
2.使用package声明类或接口所属的包,声明在源文件的首行
3.包属于标识符,遵循标识符的命名规则、规范(小写)、“见名知意”
4.每" . "一次就代表一层文件目录。
补充:同一个包一下,不能命名同一个接口、类
不同的包下,可以命名同一个接口,同一个类
2、举例
某航运软件系统包括:一组域对象、GUI和reports子系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5M9lOCV-1649144014076)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210803091606899.png)]
举例二:MVC设计模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jDG8aFL-1649144014077)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210803091700417.png)]
3、jdk中主要包介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFgD0gfM-1649144014077)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210803091725785.png)]
import关键字
import:导入
-
- 在源文件中显式的使用import结构导入指定包下的类、接口
-
- 声明在包的声明和类的声明之间
-
- 如果需要导入多个结构,则并列写出即可
-
- 可以使用"xxx.*"的方式,表示可以导入xxx包下的所结构
-
- 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
-
- 如果使用的类或接口是本包下定义的,则可以省略import结构
-
- 如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
-
- 使用"xxx.*"方式表明可以调用xxx包下的所结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
-
- import static:导入指定类或接口中的静态结构:属性或方法。
六、面向对象-中
6.1 面向对象特征之二----继承性
1、为什么要有类的继承(继承性的好处)?
① 减少代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后多态性的使用,提供了前提
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Cnpy3Ep-1649144014078)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210804092720987.png)]
2、继承性的格式
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
3、子类继承父类以后有哪些不同?
3.1 体现:一旦子类A继承了父类B,子类A就获取了父类B中声明的所有结构:属性、方法
特别地,父类中声明为private的属性和方法,子类继承父类以后,仍然认为子类获取了父类的私有结构
只是因为封装性的影响,子类不能直接调用父类的私有结构而已。
3.2 子类继承父类以后还可以声明自己特有的属性和方法:实现功能的扩展
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类声明的功能
6.2 方法的重写
1、什么是方法的重写?
子类继承父类以后,可以对父类同名同参数的方法,进行覆盖操作
2、应用
重写以后,当创建子类对象以后,通过子类对象调用父类同名同参的方法时,实际执行的是子类重写父类的方法
3、举例
class Circle{
public double findArea(){}//求面积
}
class Cylinder extends Circle{
public double findArea(){}//求表面积
}
***************
class Account{
public boolean withdraw(double amt){}
}
class CheckAccount extends Account{
public boolean withdraw(double amt){}
}
4、重写的规则
① 子类重写的方法的方法名和参数列表与父类被重写的方法的方法名和参数列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
子类不能重写父类中声明为private权限的方法
③ 返回值类型
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型也是void
父类被重写的方法的返回值类型是A类,则子类重写的方法的返回值类型可以是A类或者是A类的子类
对于返回值是基本数据类型,子类和父类的返回值类型必须一致,不存在子类关系
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
5、面试题
区分方法的重写和重载?
答:
① 二者的概念:
② 重载和重写的具体规则
③ 重载:不表现为多态性。
重写:表现为多态性。
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器 而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重 载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
6.3 super关键字
1、super关键字可以理解为:父类的
2、可以用来调用的结构:属性、方法、构造器
3、super调用属性、方法
3.1 我们可以在子类的方法或构造器中,通过super.属性或super.方法的方式,
显示地调用父类中声明地属性和方法
3.2 当子类和父类定义了同名的属性时,我们想要在子类中调用父类的属性,则需要使用super.属性的方式,
表明调用的是父类的属性
子类对象实例化全过程
1.从结果上看:继承性
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所父类中声明的属性。
2.从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePPZuQ6U-1649144014079)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210805085057365.png)]
3.强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n130cObJ-1649144014079)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210805085136657.png)]
6.4 面向对象的特征之三–多态性
1、多态性的理解
可以理解为一个事物的多种形态
2、何为多态性
父类的引用指向子类的对象
举例:
package com.atscitc.java3;
public class FishTest {
public static void main(String[] args) {
FishTest test = new FishTest();
//调用总的吃这个方法,在里面传入具体的对象,执行具体的对象的具体的方法
test.func(new shark());
test.func(new carp());
// shark sh = new shark();
// sh.eat();
// Fish fish = new Fish();
// fish.eat();
}
//把总的吃这个方法写入一个方法,设置一个父类类型的参数
public void func(Fish fish) {
fish.eat();
}
}
class Fish {
public void eat() {
System.out.println("吃");
}
}
class shark extends Fish {
public void eat() {
System.out.println("鲨鱼吃小鱼");
}
}
class carp extends Fish {
public void eat() {
System.out.println("小鱼吃虾米");
}
}
3、多态性的使用:虚拟方法的调用
有了对象的多态性以后,我们在编译期只能调用父类的方法,但是在运行期,执行的是子类重写父类的方法
总结:编译,看左边;运行,看右边
4、多态性的使用前提
① 继承性
② 方法的重写
5、多态性使用的注意点
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
7、关于向上或向下转型
7.1 向上转型:多态
7.2 向下转型
7.2.1 为什么使用向下转型?
有了对象的多态性以后,内存中实际上是加载了子类的属性和方法的,但是声明的变量是父类类型的,导致编译时就不能调用子类的属性和方法,只能调用父类的属性和方法。那么如何才能调用子类的属性和方法呢?
使用向下转型
7.2.2 如何实现向下转型?
使用强制类型转换符:()
// 那么如何调用子类特有的属性和方法呢?
// 向下转型:使用强制转换符,将Person类型的p2转换称Man类型
// Person p1 = new Person();
Man man2 = (Man) p2;
man2.isSmoking = true;
man2.earnMoney();
7.2.3 使用强转时的注意点:
① 使用强转时可能会出现 ClassCastException 异常 — 类型转换异常
② 为了避免向下转型时出现ClassCastException 异常 — 类型转换异常,在进行向下转型时,先进行instanceof判断。如果返回 true, 就向下转型,如果返回false,就终止向下转型
7.2.4 instanceof的使用 :
a instanceof A : 判断a是否是类A的实例 (是返回true;不是返回 false)
7.2.5 图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IUwzwJEy-1649144014080)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210806132436788.png)]
面试题:谈谈你对多态的理解:
① 实现代码的通用性
② 抽象类、接口的使用体现了多态性(抽象类、接口不能实例化)
6.5 Object类的使用
1、关于Object类的说明:
1.Object类是所有Java类的根父类
2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为Java.lang.Object类
3.Object类中的功能(属性、方法)就具有通用性
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()进行重写
重写的原则:比较两个对象的实体内容是否相同
重写之后的equals()比较同一个类的两个不同对象,结果为true,两个不同类的不同对象结果为false
2.2 如何重写equals()
2.2.1 手动重写举例:
class User{
String name;
int age;
//重写其equals()方法
public boolean equals(Object obj){
if(obj == this){
return true;
}
if(obj instanceof User){
User u = (User)obj;
return this.age == u.age && this.name.equals(u.name);
}
return false;
}
}
2.2.2 在开发中:自动生成
2.3 回顾 == 运算符的使用:
1.可以使用在基本类型数据类型变量和引用数据类型变量中
2.如果比价的是基本数据类型,那么比较的是两个变量保存的数据是否相等()
如果比较的是引用数据类型,比较两个对象的地址值是否相同,即两个应用是否指向同一个对象实体
3.1 toString()的使用
1.当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
2.Object类中toString的定义:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
3.像String、Date、File、包装类等,都重写了Object类中的toString(),
使得在调用对象的toString(),返回实体内容
3.2 如何重写toString()
@Override
public String toString() {
return "Customer [name = " + name + ", age = " + age + "]";
}
4. 单元测试方法的使用
java中的JUnit测试
- 步骤:
- 1.选中当前工程 - 右键选择:buid 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.如果出现异常:红条
5.包装类的使用
5.1 为什么要使用包装类?
1.Java提供了8中基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
2.掌握的:基本数据类型、包装类、String之间的相互转换
5.2 基本数据类型与对应的包装类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUY5p2RB-1649144014080)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210806143926104.png)]
5.3 需要掌握的类型间的转换:(基本数据类型、包装类、String)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OO4uRhoc-1649144014081)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210806144255252.png)]
简易版:
基本数据类型<—>包装类:JDK 5.0 新特性:自动装箱 与自动拆箱
基本数据类型、包装类—>String:调用String重载的valueOf(Xxx xxx)
String—>基本数据类型、包装类:调用包装类的parseXxx(String s)
注意:转换时,可能会报NumberFormatException
应用场景举例:
① Vector类中关于添加元素,只定义了形参为Object类型的方法:
v.addElement(Object obj); //基本数据类型 —>包装类 —>使用多态
七、面向对象-下
7.1 关键字static
1、可以用来修饰的结构:主要用来修饰类的内部结构
属性、方法、代码块、内部类
2、使用static修饰属性:静态变量(类变量)
2.1 属性,按是否使用static修饰,又分为静态属性和非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性。当修改其中一个对象
的非静态属性时,不会导致其他对象中同样的属性值修改
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,
会导致其他对象调用此变量时,值是修改过了的。
2.2 关于静态变量的一些其他说明
① 静态变量随着类的加载而加载,可以通过"类.静态变量"方式调用静态变量
② 静态变量的加载早于对象的创建
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
3、静态变量内存解析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yd6xrscQ-1649144014081)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210807122949721.png)]
4、static修饰方法:静态方法
① 静态方法随着类的加载而加载,可以通过"类.静态方法"的方式调用静态方法
② 静态方法中,只能调用静态属性或方法
非静态方法中,既可以调用非静态方法和属性,也可以调用静态方法和属性
5、static的注意点
在静态方法中不能使用this关键字、super关键字
6、如何判定属性和方法应该使用static关键字:
6.1 关于属性
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常常声明为static
6.2 关于方法
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
7.使用举例:
举例一:Arrays、Math、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;
}
}
7.2 单例模式
1、设计模式的说明
1.1 理解:设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。
1.2 常用设计模式 — 23种经典的设计模式 GOF
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2、单例模式
2.1 要解决的问题:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
2.2 具体代码的实现:
2.2.1:饿汉式
/*
* 单例模式的饿汉式实现
* 太饿了,上来就new饭吃
*
*
* 我们首先必须将类的构
造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生
类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无
法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,
静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象
的变量也必须定义成静态的。
*/
public class SingletonTest1 {
public static void main(String[] args) {
// Bank bank = new Bank();
// 5.外部通过"类名.方法"的方式调用类内部创建对象的方法,从而获得对象
Bank bank = Bank.getBank();
Bank bank1 = Bank.getBank();
Bank bank2 = Bank.getBank();
System.out.println(bank == bank2);// true
}
}
// 饿汉式单例模式
class Bank {
// 1.首先必须将类的构
// 造器的访问权限设置为private
private Bank() {
}
// 2.在类的内部创建一个对象
// 4.由于static(静态方法只能调用静态变量,所以bank变量也要加上static)
static Bank bank = new Bank();
// 3.在类的内部创建一个方法,返回内部创建的对象(由于构造方法是private,外部无法直接用new操作符在类的外部产生
// 类的对象,所以用static关键字,让外部能够使用"类名.方法"的方式调用内部方法,从而得到内部创建的对象)
public static Bank getBank() {
return bank;
}
}
2.2.2:懒汉式
/*
* 单例模式的懒汉式实现
*
* 懒汉式:太懒了没得吃,肚子为null
*
* 区分饿汉式和懒汉式?
* 1.饿汉式:
* 好处:对象加载时间过长
* 坏处:天然的线程安全
* 2.懒汉式:延迟对象的创建
* 目前的写法坏处:线程不安全
*/
public class SingletonTest2 {
public static void main(String[] args) {
Lazy lazy = Lazy.getLazy();
Lazy lazy1 = Lazy.getLazy();
Lazy lazy2 = Lazy.getLazy();
System.out.println(lazy == lazy1 && lazy1 == lazy2 && lazy == lazy2);// true
}
}
class Lazy {
private Lazy() {
}
private static Lazy lazy = null;
public static Lazy getLazy() {// 这里要加上static,外部才能通过"类名.方法"的方式调用方法,获取对象
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
2.3 两种方式的对比
饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
懒汉式:好处:延迟对象的创建。
目前的写法坏处:线程不安全。—>到多线程内容时,再修改
7.3 类的成员之四–代码块
1、代码块的作用:用来初始化类、对象的信息
2、分类:代码块要是使用修饰符,只能使用static
静态代码块:
-
可以有输出语句
-
随着类的加载而执行,而且只执行一次
-
> 如果一个类中定义了多个静态代码块,则按声明的先后顺序执行
-
> 静态代码块的执行要优先于非静态代码块的执行
-
> 静态代码块内只能调用静态属性、方法,不能调用非静态结构
非静态代码块:
-
可以有输出语句
-
随着对象的创建而执行
-
每创建一次就执行一次非静态代码块
-
如果一个类中定义了多个非静态代码块代码块,则按声明的先后顺序执行
-
非静态代码块内既能调用非静态属性、方法,也能调用静态属性、方
3、属性的赋值顺序
- ①默认初始化
- ②显式初始化/⑤在代码块中赋值
- ③构造器中初始化
- ④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
- 执行的先后顺序:① - ② / ⑤ - ③ - ④
7.4 final关键字
1、可以用来修饰:类、方法、变量
2、具体的:
2.1 final用来修饰一个类:此类不可以被其他类继承
比如:String类、system类、String Buffer类
2.2 final修饰方法,表示此方法不可以被重写
2.3 final修饰变量,此时的变量就是一个常量
2.3.1 final修饰属性,可以赋值的位置有:显示赋值、代码块中初始化、构造器初始化、
static final 用来修饰属性:全局常量
7.5 abstract关键字:抽象的
1、可以用来修饰:类、方法
2、具体的:
(1) abstract修饰类:抽象类
-
> 此类不能实例化
-
抽象类中一定有构造器,便于子类实例化调用
-
开发中,都会提供抽象类的子类,让子类实例化,完成相关操作 —>> 抽象的使用前提继承性
(2)abstract修饰方法:抽象方法
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类一定是一个抽象类。反之,抽象类中可以没有抽象方法的
-
若子类重写了父类所有的抽象方法,子类方可实例化
-
若子类没有重写父类所有的抽象方法,则此子类也是抽象方法,需加abstract修饰
(3)注意点
- 1.abstract不能用来修饰:属性、构造器等结构
- 2.abstract不能用来修饰私方法、静态方法、final的方法、final的类
3、举例一:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8GxJce4-1649144014083)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210808223556483.png)]
举例二:
abstract class GeometricObject{
public abstract double findArea();
}
class Cicle extends GeometricObject{
private double redius;
public double findArea(){
return Math.PI * redius * redius
}
}
举例三:
IO流中设计到的抽象类:InputStream/OutputStream / Reader /Writer。在其内部
定义了抽象的read()、write()方法。
4、模板设计模式
(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)应用场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-639SIipu-1649144014083)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210808225111510.png)]
7.6 接口
1、使用说明
interface的使用:
-
1.接口使用interface来定义
-
2.Java中,接口和类是并列的两个结构
-
3.如何定义接口:定义接口中的成员
-
3.1 JDK7及以前:只能定义全局常量和抽象方法
-
全局常量:public static final 的,但是书写时,可以省略不写*
-
抽象方法:public abstract 的
-
3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
-
4.接口中不能定义构造器的!意味着接口不能实例化
-
5.Java开发中,接口通过让类去实现(implements)的方式来使用
-
如果实现类覆盖了接口中所有的抽象方法,则此实现类就可以实例化
-
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍然是一个抽象类
-
6.Java可以实现多个接口---->>打破了Java单继承的局限性
-
格式:class AA extends BB implements CC,DD,EE
-
7.接口与接口之间可以继承,而且是多继承
2、举例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhhdzoWj-1649144014084)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210808225650032.png)]
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、Java8中关于接口的新规范
//知识点1:接口中定义的静态方法,只能通过接口来调用。//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法//知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则
//知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没重写此方法的情况下,报错。–>接口冲突。
//这就需要我们必须在实现类中重写此方法
//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法public void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); }
4、面试题
抽象类和接口的异同?
相同点:不能实例化;都可以包含抽象方法的。不同点:
1)把抽象类和接口(java7,java8,java9)的定义、内部结构解释说明
2)类:单继承性 接口:多继承
类与接口:多实现5、代理模式
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、应用场景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-duZK0ThA-1649144014084)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210808231627630.png)]
7.7 内部类
1、定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类.
2、内部类的分类:
成员内部类(静态、非静态 ) vs 局部内部类(方法内、代码块内、构造器内)
3、成员内部类的理解:
一方面,作为外部类的成员:
-
>调用外部类的结构
-
>可以被static修饰
-
>可以被4种不同的权限修饰
另一方面,作为一个类:
-
> 类内可以定义属性、方法、构造器等
-
> 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
-
可以被abstract修饰
4、成员内部类
4.1如何创建成员内部类的对象?(静态的,非静态的)
//创建静态的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 = "小明";
public void eat(){
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
//Person.this.eat();
}
}
}
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;
}
}
}
注意点:
在局部内部类的方法中(比如:show如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
*
jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明
总结:
成员内部类和局部内部类,在编译以后,都会生成字节码文件。
格式:成员内部类:外部类
内
部
类
名
.
c
l
a
s
s
局
部
内
部
类
:
外
部
类
内部类名.class 局部内部类:外部类
内部类名.class局部内部类:外部类数字 内部类名.class
八、异常
8.1异常
8.1.1异常的体系结构
- Java.lang.Thowable:
-
|------java.lang.Error:一般不编写针对性的代码进行处理
|------java.lang.Exception:可以进行异常处理
|------编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|------运行时异常(unchecked)
|-----NullPointerException
|-----ArrayIndexOutOfBoundException
|-----ClassCastException
|-----NumberFormatException
|-----InputMismatchException
|-----ArithmetiException
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9kFyO2h-1649144014085)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210809204625890.png)]
8.2.2.从程序执行过程,看编译时异常和运行时异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EOdDiGmt-1649144014086)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210809212418217.png)]
编译时异常:执行javac.exe命名时,可能出现的异常
运行时异常:执行java.exe命名时,出现的异常
8.2.3常见的异常类型
public class ExceptionTest {
// ArithmetiException:算术计算异常
@Test
public void test6() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
// InputMismatchException:输入不匹配异常
@Test
public void test5() {
Scanner sc = new Scanner(System.in);
int score = sc.nextInt();
System.out.println(score);
}
// NumberFormatException:数值类型转换异常
@Test
public void test4() {
String string = "abcd";
int parseInt = Integer.parseInt(string);
}
// ClassCastException:类型转换异常
@Test
public void test3() {
Object obj = new Date();
String s = (String) obj;
}
// ArrayIndexOutOfBoundException:数组下标越界
@Test
public void test2() {
int[] arry = new int[10];
System.out.println(arry[10]);
}
// NullPointerException:空指针异常
@Test
public void test1() {
int[] num = null;
System.out.println(num[0]);//
}
}
8.2异常的处理
8.2.1异常处理的抓抛模型
-
异常的处理:抓抛模型
-
过程一:“抛” — >> 程序在正常执行过程中,一旦出现异常,就会在异常代码处,生成一个对应的异常对象
-
并将此对象抛出
-
关于异常对象的产生:① 系统自动生成的异常对象
-
② 手动生成一个异常对象,并抛出(throw)
-
过程二:"抓 ----->>> 可以理解为异常的处理方式:① try-catch-finally ② throws
8.2.2异常处理的方式一:try-catch-finally
使用说明
-
try{
-
//可能出现异常的代码
-
}catch(异常类型1 变量名1){
-
//处理异常的方式1
-
}catch(异常类型2 变量名2){
-
//处理异常的方式2
-
}catch(异常类型3 变量名3){
-
//处理异常的方式3
-
}
-
…
-
finally{
-
//一定会执行的代码
-
}
-
1.finally是可选的
-
2.使用try将可能出现异常的代码包起来,在执行过程中,一旦出现异常,就会出现一个对应的异常类的对象,
-
根据此对象的类型,去catch中进行匹配
-
3.一旦try中的异常对象匹配到某个catch时,就进入catch中进行异常的处理,一旦处理完成就跳出try-catch结构,
-
(在没有finally的情况),继续执行其后代码。
-
4.catch中的异常,如果没有字符类关系,则谁声明在上,谁声明在下无所谓
-
catch中如果满足子父类的关系,则要求子类声明在父类的上面
-
5.常用的异常处理方式:① e.getMessage() ② e.printStackTrace()
-
6.在try中声明的变量,出了try结构中,就不能在调用
总结:如何看待代码中的编译时异常和运行时异常?
- 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
- 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
8.2.3finally再说明
- 1.finally是可选的
- 2.finally中声明的是一定会被执行的代码。即使catch中又出现了异常,try中有return语句,catch有return语句的
-
情况,finally中的语句也一样会被执行
- 3.向数据库连接、输入输出流、网络编程的socket等资源,JVM是不能自动回收的,我们需要自己手动的进行
-
资源的释放。此时的资源释放就需要放在finally里面。
面试题
final、finally、finalize三者的区别?
类似:
throw 和 throws
Collection 和 Collections
String 、StringBuffer、StringBuilder
ArrayList 、 LinkedList
HashMap 、LinkedHashMap
重写、重载
结构不相似的:
抽象类、接口
== 、 equals()
sleep()、wait()
8.2.4异常处理方式二
"throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
8.2.5对比处理两种方式
try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没真正将异常处理掉
8.2.7体会开发中应该如何选择两种处理方式?
- 5.1 如果父类中被重写的方法没throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中异常,必须使用try-catch-finally方式处理。
- 5.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
补充:
方法重写的规则之一:
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
8.3手动抛出异常
8.3.1使用说明
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象。
8.3.2面试题
throw 和 throws区别:
throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dh2HHraj-1649144014086)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210904163942269.png)]
8.3.3典型例题
public class EcmDef {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
EcmDef.ecm(i, j);
} catch (NumberFormatException e) {
System.out.println("分子和分母的数据类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺 少 命 令 行 参 数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (ECDef e) {
System.out.println(e.getMessage());
}
}
public static double ecm(double i, double j) throws ECDef {
if (i < 0 || j < 0) {
throw new ECDef("分子或分母为负数了!");
}
return i / j;
}
}
class ECDef extends Exception {
static final long serialVersionUID = -70348975766939L;
public ECDef() {
}
public ECDef(String msg) {
super(msg);
}
}
8.4自定义异常类
8.4.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);
}
}
第二部分:Java高级编程
一、多线程
1、程序、进程、线程的理解
(1)程序:是为完成特定任务、用某种语言编写的一组指令的集合。
说明:即指一 段静态的代码,静态对象。
(2)进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。
说明:程序是静态的,进程是动态的,进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
(3)线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小
2、并行与并发
(1)单核CPU与多核CPU的理解
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
(2)并行与并发的理解
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
3、创建多线程的两种方式
方式一:继承Thread类的方式
- 1.创建一个继承于Thread类的子类
- 2.重写Thread类的run() ---->> 将此线程执行的操作写在方法体中
- 3.创建Thread类的子类的对象
- 4.通过此对象调用star()
说明两个问题:
问题一:我们启动一个线程,必须用start(),而不能用run()的方式启动线程
问题二:如果在启动一个线程,必须在创建一个Thread子类的对象,调用此对象的start()
方式二:实现Runnable接口的方式
- 1.创建一个实现了Runnable接口的类
- 2.实现类去实现Runnable中的抽象方法:run()
- 3.创建实现类的对象
- 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 5.通过Thread类的对象调用start()
两种方式的对比:
- 开发中,优先选择Rannable的接口的方式
- 原因:① 实现的方式没有类的单继承性的局限性
- ② 实现的方式更适合处理多个线程有共享数据的情况
- 联系:
- public class Thread implements Runnable
- 都需要重写run().把要执行的操作放进run()的方法体中
4、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():让当前线程睡眠,()里面填入指定的毫秒数
- 10.isAlive():判断当前线程是否存活
-
- 线程的优先级:
- 1.Thread.MAX_PRIORITY:10
- 2.Thread.MIN_PRIORITY:1
- 3.NORM_PRIORITY:5 — >> 默认优先级
-
- 如何获取和设置线程的优先级:
- 1.设置线程优先级:h1.setPriority(Thread.MAX_PRIORITY);
- 2.获取线程优先级:Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
-
- 说明:优先级高的线程会抢占优先级低的线程的CPU执行权,但是只是从概率上讲,优先级高的线程有很大概率会被优先执行
- 但并不意味着,一定要执行完优先级高的线程才执行优先级低的,优先级低的抢到资源也会执行。
5、线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFkz7m3T-1649144014087)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210812204301426.png)]
6、线程的同步机制
6.1背景
例子:创建三个窗口卖票
- 出现重票,错票线程安全问题
- 1.出现的原因:当某个线程操作车票的过程中,尚未执行完操作,其他线程也参与进来,操作车票
- 2.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到线程a操作完成之后,其他线程才能参与进来
- 这种情况就算线程a出现了阻塞,也不能改变。
- 3.在Java中,我们通过同步机制,来解决线程安全问题
6.2 在Java中怎么解决:同步机制
方式一:同步代码块
-
synchronized (同步监视器) {
-
//需要被同步的代码块
-
}
-
说明:1.操作数据的共享代码,就是需要被同步的代码
-
2.多个线程共同操作的变量
-
3.同步监视器:锁,任何一个类的对象都可以充当同步监视器
-
要求:多个线程必须共用一把锁
-
4.使用同步的方式虽然解决了线程安全问题,但是在同步代码里面相当于是一个单线程,效率比较低
方式二:同步方法
-
如果操作共享数据的代码完整的声明在一个方法中,那么就可以将此方法声明为同步
方式三:
-
解决线程安全问题的方式三:Lock锁----jdk5.0新增
-
面试题:synchronized和 Lock的异同?
-
同:① 都可以解决线程安全问题
-
不同:① synchronized在执行完相应的同步代码块之后,自动释放同步监视器
② LOck需要手动启动同步(Lock()),,同时结束同步也需要是手动实现(unlock())
关于同步方法的总结:
- 1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 2.静态同步方法的同步监视器:当前类本身
- 3.非静态同步方法的同步监视器:this
7、线程安全问题懒汉式
class Lazy{
private Lazy(){
}
private static Lazy lazy = null;
// 方式二:效率低
// public static synchronized Lazy lazy(){
// 方式一:效率高
public static Lazy lazy(){
synchronized(Lazy.class){
if(lazy == null){
lazy = new Lazy();
}
}
return lazy;
}
}
8、线程通信
8.1 涉及到的三个方法
-
线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
-
涉及到的三个方法:
-
1.wait():一旦执行此方法,线程就进入阻塞状态,并释放同步监视器
-
2.notify():一旦执行此方法,就会唤醒被wait的线程。如果有多个线程被wait,就唤醒优先级高的哪个
-
3.notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
-
1.以上三个方法,只能在同步代码块或者同步方法中使用
-
2.以上三个方法的调用者,必须是同步方法或者同步代码块的同步监视器,否则会出现java.lang.IllegalMonitorStateException–非法监视的异常
-
3.以上三个方法是定义在java.lang.Object类中的
面试题
- sleep() 和 wait() 的异同?
- 1.相同:一旦执行,都会使当前线程进入阻塞状态
- 2.不同:① Thread类中声明的sleep(),Object类中声明的wait()
-
②调用的要求不同:sleep():可以在任何被需要的场景下调用。wait():需要声明在同步方法或者同步代码块中,使用同步监视器进行调用
- ③关于是否释放同步监视器的不同:sleep():不会释放同步监视器,而wait()会释放同步监视器
8.2释放锁的操作
(1)当前线程的同步方法、同步代码块执行结束。
(2)当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。
8.3不会释放锁的操作
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行
(2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。
(3)应尽量避免使用suspend()和resume()来控制线程
9、jdk5.0新增线程的创建方式
9.1 新增方式一:实现callable接口
- 步骤:
- 1.创建实现Callable接口的实现类
- 2.实现call方法,将此线程要执行的操作声明在call()中
- 3.创建callable接口实现类的对象
- 4.将callable实现类的对象作为参数传递到FutureTask构造器当中,创建FutureTask类的对象
- 5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,并调用start()
- 6.通过FutureTask类的对象,调用get()方法获取callable类中call()的返回值
9.2 如何理解通过实现callable接口创建线程的方式比通过实现Runnable接口创建线程的方式更强大?
(1)call()可以有返回值
(2)call()可以抛出异常,可以被外面的操作捕获,获取异常信息
(3)callable是支持泛型的
9.2 新增方式二:线程池
package com.atscitc.java2;
import java.sql.Time;
import java.util.concurrent.*;
/**
* 创建线程的方式四:
* 好处:
* ① 提高响应的速度
* ② 节约资源
* ③ 便于管理
* // 设置核心池的大小
* service1.setCorePoolSize(20);
* // 获取线程池的大小
* int corePoolSize = service1.getCorePoolSize();
* System.out.println("线程池的大小为:" + corePoolSize);
* <p>
* // 设置线程池的最大线程数
* service1.setMaximumPoolSize(20);
* // 获取线程池的最大线程数
* int maximumPoolSize = service1.getMaximumPoolSize();
* System.out.println("线程池的最大线程数为:" + maximumPoolSize);
* <p>
* //线程没有任务时保持多久会终止
* // service1.setKeepAliveTime()
* <p>
* *面试题**
* 创建多线程的方式有几种?
* 4种
*
* @author master
* @create 2021-08-12 18:53
*/
class NumThread implements Runnable {
@Override
public void run() {
for (int i = 1; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumThread1 implements Runnable {
@Override
public void run() {
for (int i = 1; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumThread2 implements Callable {
int A;
@Override
public Object call() throws Exception {
for (int i = 1; i <= 100; i++) {
if (i == 'A') {
System.out.println(Thread.currentThread().getName() + "等于A的数字为:" + i);
A = i;
}
}
return A;
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// 设置核心池的大小
service1.setCorePoolSize(20);
// 获取线程池的大小
int corePoolSize = service1.getCorePoolSize();
System.out.println("线程池的大小为:" + corePoolSize);
// 设置线程池的最大线程数
service1.setMaximumPoolSize(20);
// 获取线程池的最大线程数
int maximumPoolSize = service1.getMaximumPoolSize();
System.out.println("线程池的最大线程数为:" + maximumPoolSize);
//线程没有任务时保持多久会终止
// service1.setKeepAliveTime()
// 1.执行指定线程的操作,需要提供实现Runnable接口或者Callable接口的实现类的对象
service.execute(new NumThread());//适合用于Runnable
service.execute(new NumThread1());//适合用于Runnable
// 创建futureTask类的对象
FutureTask futureTask = new FutureTask(new NumThread2());
// 将FutureTask对象作为参数传递给service.submit()
service.submit(futureTask);//适合用于Callable
// new Thread(futureTask).start();
Object C = null;
try {
C = futureTask.get();
System.out.println("数值为A的数字为:" + C);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdown();//关闭线程池
}
}
面试题
创建多线程的方式有几种?
4种
二、Java常用类
1、Java String类的使用
1.1、概述
String:字符串,使用一对""来表示
1.String声明为final的,不可被继承
2.String声明了Serializable接口:表示字符串是支持序列化的
实现了Comparable:表四String可以比较大小
3.String内部定义了final char[] value 用于存储字符串数据
4.String代表不可变的字符序列,不可变性
1.2、不可变性
不可变性的体现:
(1)当对字符串重新赋值时,需要重新指定内存区域进行赋值,不能在原有的value上进行修改
(2)当对现有的字符串进行拼接时,也需要重新指定内存区域进行赋值,不能在原有的value上进行修改
代码举例:
@Test
public void test1() {
String s = "abc";
String b = "abc";
s = "edf";
System.out.println("s的值为:" + s);//edf
System.out.println("b的值为:" + b);//abc
System.out.println(s == b);//false
String c = "abc";
c += "def";
System.out.println("c的值为:" + c);//abcdef
System.out.println("此时b的值为:" + b);//abc
System.out.println(c == b); // false 因为是新造了一块内存区域所以地址值不相等
}
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UofGZbNh-1649144014088)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210814202949308.png)]
1.3、面试题:
通过String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
2个。一个是在堆空间中new的结构;另一个char[] 对应的字符串常量池的中的数据:“abc”
1.4、字符串拼接方式赋值的对比
结论:
1.常量与常量的拼接在常量池,且常量池不会存在相同的数据
2.只要其中有一个是变量。其结果就在堆中
3.如果拼接的结果调用intern(),返回的结果就在常量池当中
1.5、代码示例
@Test
public void test3(){
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
}
1.6、String类的常用方法
* String常用方法1
* int length():返回字符串的长度: return value.length
* char charAt(int index): 返回某索引处的字符return value[index]
* boolean isEmpty():判断是否是空字符串:return value.length == 0
* String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
* String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
* String trim():返回字符串的副本,忽略前导空白和尾部空白
* boolean equals(Object obj):比较字符串的内容是否相同
* boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大
* 小写
* String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
* int compareTo(String anotherString):比较两个字符串的大小
* String substring(int beginIndex):返回一个新的字符串,它是此字符串的从
* beginIndex开始截取到最后的一个子字符串。
* String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字
* 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。[)
*
* @author master
* @create 2021-08-13 18:02
*/
public class StringMethodTest1 {
@Test
public void test2() {
String s = "helloworld";
String substring = s.substring(5);
System.out.println(substring);
String substring1 = s.substring(5, 10);//[):左闭右开,不包含最后一个字符串
System.out.println(substring1);
}
@Test
public void test1() {
String s = "helloworld";
System.out.println(s.length());
System.out.println(s.charAt(0));
System.out.println(s.isEmpty());
String s1 = "HELLOWORLD";
System.out.println(s1.toLowerCase());
System.out.println(s.toUpperCase());
String s2 = " hello world ";
System.out.println(s2.trim());
System.out.println(s.equals(s2));
System.out.println("----------------------");
System.out.println(s.equalsIgnoreCase(s1));
System.out.println(s.equalsIgnoreCase(s2));
System.out.println("----------------------");
System.out.println(s.concat(s1));
System.out.println("----------------------");
System.out.println(s.compareTo(s1));// ASCll表
}
}
/**
* boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
* boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
* boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的
* 子字符串是否以指定前缀开始
* boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列
* 时,返回 true
* int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
* int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出
* 现处的索引,从指定的索引开始
* int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
* int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后
* 一次出现处的索引,从指定的索引开始反向搜索
* 注:indexOf和lastIndexOf方法如果未找到都是返回-1
* <p>
* 什么情况下,indexOf(str)和lastIndexOf(str)的返回值相同?
* 情况一:存在唯一的str
* 情况二:不存在返回-1
*
* @author master
* @create 2021-08-13 18:37
*/
public class StringMethodTest2 {
@Test
public void test3() {
String s = "helloworld";
int i = s.lastIndexOf("w", 9);//从后往前找,但返回的索引,依旧是从前往后数
System.out.println(i);
}
@Test
public void test2() {
String s = "helloworld";
String s1 = "ll";
boolean contains = s.contains(s1);
System.out.println(contains);
int i = s.indexOf("l");
System.out.println("l第一次出现的位置为:" + i);
int l = s.indexOf("l", 4);
System.out.println(l);
}
@Test
public void test1() {
String s = "helloworld";
boolean s1 = s.endsWith("d");
System.out.println(s1);
boolean h = s.startsWith("h");
System.out.println(h);
boolean b = s.startsWith("l", 8);
System.out.println(b);
}
}
/**
* String replace(char oldChar, char newChar):返回一个新的字符串,它是
* 通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
* String replace(CharSequence target, CharSequence replacement):使
* 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
* String replaceAll(String regex, String replacement) : 使 用 给 定 的
* replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
* String replaceFirst(String regex, String replacement) : 使 用 给 定 的
* <p>
* replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
* <p>
* boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
* String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
* String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此
* 字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
*
* @author master
* @create 2021-08-13 19:25
*/
public class StringMethodTest3 {
@Test
public void test1() {
String str = "四川信息职业技术学院,四川";
String replace1 = str.replace("院", "校");
System.out.println(replace1);
String replace = str.replace("四川", "广元");
System.out.println(replace);
}
}
1.7、String与基本数据类型、包装类之间的转换
/**
* String与其他结构之间的转换
* 1.String -->> char[]:调用String的toCharArray()
* 2.char[] --->> String:调用String的构造器,将char[]的对象作为参数传入String的()
* 3.String --->> byte[]:调用String的getBytes() ---- 编码
* 4.byte[] --->> String:调用String的构造器
*
* @author master
* @create 2021-08-13 19:49
*/
public class StringTest {
// byte[] --->> String:调用String的构造器
@Test
public void test5() {
String s = "hello中国";
byte[] bytes = new byte[0];
try {
bytes = s.getBytes("gbk");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(Arrays.toString(bytes));
// String s1 = new String(bytes); 和编码用的字符集不一致造成乱码
String s1 = null;
try {
s1 = new String(bytes, "gbk");//指定解码的字符集格式,和编码字符集一致
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(s1);// hello中国
// System.out.println(s1);
}
// String --->> byte[]:调用String的getBytes() ---- 编码
@Test
public void test4() {
String str = "helloworld";
byte[] bytes = str.getBytes();
// for (int i = 0; i < bytes.length; i++) {
// System.out.println(bytes[i]);//十进制数
// }
System.out.println(Arrays.toString(bytes));//十进制数
}
// char[] --->> String:调用String的构造器,将char[]的对象作为参数传入String的()
@Test
public void test3() {
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
String s = new String(arr);
System.out.println(s);// hello
}
// String 与 char[] 之间的转换
//String -->> char[]:调用String的toCharArray()
@Test
public void test2() {
String str = "123helloworld";
char[] chars = str.toCharArray();
System.out.println(chars);
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i]);
}
}
//String与基本数据类型、包装类之间的转换
@Test
public void test1() {
String s = "124";
int num = Integer.parseInt(s);
System.out.println(num);
String s1 = String.valueOf(num);
System.out.println(s1 instanceof Object);
String s2 = num + "";//涉及到变量,存放在堆空间,s是字面量赋值所以在常量池,一个在堆空间指向
// 常量池中的"124",一个就在常量池,所以地址值不相等没结果为false
System.out.println(s == s2);
}
}
2、StringBuffer、StringBuild的使用
2.1、StringBuffer、StringBuild的解析
源码分析:
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)
2.2、StringBuffer的常用方法:
StringBuffer的常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
总结:
增: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()
2.3、String、StringBuffer和StringBuilder的区别?
String:不可变的字符序列,底层使用char[]存储
StringBuffer:可变的字符序列,线程安全,效率低,底层使用char[]存储
StringBuilder:可变的字符序列,线程不安全,效率高,底层使用char[]存储
3、JDK8之前日期、时间API
3.1、获取系统当前的时间:System类中的currentTimeMillis()
// 返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
long time = System.currentTimeMillis();
System.out.println(time);
3.2、Java.util.Date类和Java.sql.Date类
一、两个构造器的使用
1.getTime():获取当前Date对象对应的毫秒数
2.构造器二:创建指定对象的毫秒数Date对象
二、两个方法的使用
1.date.toString():显示当前的年月日、时分秒
2.date.getTime():获取当前Date对象对应的毫秒数
3.4、simpleDateFormat类的使用:对日期Date类的格式化和解析
两个操作:
1.格式化:日期 -->> 字符串
2.解析:格式化逆过程:字符串 -->> 日期
实例化:
@Test
public void testSimpleDateFormat() throws ParseException {
// 实例化:调用无参构造器实例化
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化:format
Date date = new Date();
System.out.println(date);// Sun Aug 15 17:20:12 CST 2021
String format = sdf.format(date);
System.out.println(format);// 21-8-15 下午5:19
// 解析:parse
String date1 = "21-8-15 下午5:23";
Date parse = sdf.parse(date1);
System.out.println(parse);
// 实例化二:
// SimpleDateFormat sdf1 = new SimpleDateFormat("\"h:mm a\"");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyy-MM-dd hh:mm:ss");
String format1 = sdf1.format(date);
System.out.println(format1);// "5:35 下午" //2021-08-15 05:37:26
// 解析:将字符串转换成时间
Date parse1 = sdf1.parse(format1);
System.out.println(parse1); //Sun Aug 15 05:40:26 CST 2021
}
// 将字符串"2001-09-20"转换成Java.sql.Date
@Test
public void testExer() throws ParseException {
String birth = "2001-09-20";
SimpleDateFormat birthSimple = new SimpleDateFormat("yyy-MM-dd");
Date parse = birthSimple.parse(birth);
java.sql.Date birthdate = new java.sql.Date(parse.getTime());
System.out.println(birthdate);
}
3.5、Calendar类的使用
@Test
public void test() {
Calendar calendar = Calendar.getInstance();
// get()
int days = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println("今天是今年的第" + days + "天");
System.out.println(calendar.get(Calendar.DAY_OF_WEEK));
System.out.println("今天是这个月的第" + calendar.get(Calendar.DAY_OF_MONTH) + "天");
// set()
calendar.set(Calendar.DAY_OF_MONTH, 31);//将今天是这个月的第几天改为第31天
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add
calendar.add(Calendar.DAY_OF_MONTH, 3);//在31的基础上加上3天
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
// gettime()---日历类转换为Date
Date time = calendar.getTime();
System.out.println(time);
// settime()---Date转换成日历类
Date date = new Date();
calendar.setTime(date);
System.out.println("今天是这个月的第" + calendar.get(Calendar.DAY_OF_MONTH) + "天");
}
4、JDK8中新日期时间API
4.1、日期API的迭代
第一代:jdk 1.0 Date 类
第二代:jdk 1.1 calendar 类,一定程度上替换Date类
第三代:jdk 1.8 提出了一套新的API
4.2、LocalDate、LocalTime、LocalDateTime的使用:
说明:
1.不可变性
2.LocalDateTime相较于LocalDate、LocalTime使用频率较高
3.类似于calendar
常用方法以及对应的代码演示:
(1)// 调用now()方法:获取当前日期、获取当前时间、获取当前日期、时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);// 2021-08-15
System.out.println(localTime);// 21:01:27.516
System.out.println(localDateTime);// 2021-08-15T21:01:27.516
(2)// of():获取指定的年、月、日、时、分、秒,没有偏移量。
LocalDateTime ldt = LocalDateTime.of(2001, 9, 20, 13, 14, 13);
System.out.println(ldt);
(3)// getXXX():获取相关属性
System.out.println(ldt.getYear());
System.out.println(ldt.getMonth());
System.out.println(ldt.getDayOfMonth());
System.out.println(ldt.getDayOfWeek());
System.out.println(ldt.getDayOfYear());
System.out.println(ldt.getSecond());
(4)// withXXX():设置相关属性:对日期时间进行设置 – 返回一个新的日期时间,原来的时间日期没有改变,体现不可变性
LocalDateTime localDateTime1 = ldt.withYear(2021);
System.out.println("本来的时间:" + ldt);
System.out.println("修改后的返回值为:" + localDateTime1);
(5)// plus:加:不可变性
LocalDateTime localDateTime2 = ldt.plusYears(20);
System.out.println(ldt);
System.out.println(localDateTime2);
(6)// minus:减:不可变性
LocalDateTime localDateTime3 = ldt.minusHours(3);
System.out.println(ldt);
System.out.println(localDateTime3);
4.3、Instant(瞬时点)的使用
常用方法及对应代码举例:
(1)// now():获取英国格林威治时间
Instant now = Instant.now();// 英国格林威治时间
System.out.println(now);
(2)// atOffset():根据时区调整时间的偏移量
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));//在英国格林威治时间的基础上加上8小时(时区)
System.out.println(offsetDateTime);
(3)// toEpochMilli():获取自19701月1日0时0分0秒的毫秒数
long second = now.toEpochMilli();
System.out.println(second); //1629035579077
Instant instant = Instant.ofEpochMilli(1629035579077L);
System.out.println(instant);
4.4、日期时间格式化类:DateTimeFormatter
实例化:DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(“yyy-MM-dd hh:mm:ss”);
格式化:
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyy-MM-dd hh:mm:ss");
String format = dateTimeFormatter.format(LocalDateTime.now());
System.out.println(format);// 2021-08-15 10:15:47
解析:
TemporalAccessor parse = dateTimeFormatter.parse("202 1-08-15 10:15:47");
System.out.println(parse);
5、Java比较器
5.1、说明:
Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的
但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
5.2、自然排序:使用Comparable接口
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3.重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
在compareTo(obj)方法中指明如何排序
5.举例代码:
public class Goods implements Comparable {
private String name;
private int price;
public Goods() {
}
public Goods(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
if (this.price > goods.price) {
return 1;
} else if (this.price < goods.price) {
return -1;
} else if (this.price == goods.price) {
return this.name.compareTo(goods.name);
}
}
return 0;
}
}
//----------------------------------------------------------------------------------------------------
public class Compare {
@Test
public void test1() {
String[] arr = new String[]{"FF", "AA", "JJ", "MM", "GG"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); //[AA, FF, GG, JJ, MM]
}
@Test
public void test2() {
Goods[] goods = new Goods[4];
goods[0] = new Goods("LenovoMouse", 34);
goods[1] = new Goods("MiMouse", 45);
goods[2] = new Goods("HuaWeiMouse", 90);
goods[3] = new Goods("MicrosoftMouse", 90);
Arrays.sort(goods);
System.out.println(Arrays.toString(goods));
}
@Test
public void test3() {
String[] arr = new String[]{"FF", "AA", "JJ", "MM", "GG"};
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据不一致!");
}
});
System.out.println(Arrays.toString(arr));
}
@Test
public void test4() {
Goods[] goods = new Goods[5];
goods[0] = new Goods("LenovoMouse", 34);
goods[1] = new Goods("MiMouse", 45);
goods[2] = new Goods("HuaWeiMouse", 90);
goods[3] = new Goods("HuaWeiMouse", 224);
goods[4] = new Goods("MicrosoftMouse", 90);
Arrays.sort(goods, 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("输入数据类型不一致!");
}
});
System.out.println(Arrays.toString(goods));
}
@Test
public void test5() {
String property = System.getProperty("user.name");
System.out.println(property);
}
三、枚举类和注解
1、枚举类的使用
1、枚举类的说明
-
枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
-
当需要定义一组常量时,强烈建议使用枚举类
-
如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
2、如何自定义枚举类
class Season {
// 1.声明Season类对象的属性:用pricate final修饰
private final String seasonName;
private final String seasonDesc;
// 2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 3.提供当前枚举类的多个对象:public final static的
public final static Season SPRING = new Season("春天", "穿暖花开");
public final static Season SUMMER = new Season("夏天", "夏日炎炎");
public final static Season AUTUMN = new Season("秋天", "秋风瑟瑟");
public final static Season WINTER = new Season("冬天", "万里冰封");
// 4.其他诉求1:获取枚举类对象的属性值
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// 5.其他诉求2:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
3、使用enum定义枚举类
package com.atscitc.java;
/**
* 使用enum定义枚举类
* 说明:
* 1.定义的枚举类默认继承于class java.lang.Enum类
*
* @author master
* @create 2021-08-16 22:25
*/
public class SeasonTest1 {
public static void main(String[] args) {
// toString()
Season1 spring = Season1.SPRING;
System.out.println(spring.toString()); //SPRING
// 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]);
values[i].show();
}
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的对象
// 如果没有名为objName的对象则抛出异常:java.lang.IllegalArgumentException
// Season1 summer = Season1.valueOf("SUMMER1");
Season1 summer = Season1.valueOf("SUMMER");
System.out.println(summer);
summer.show();
}
}
interface Info {
public abstract void show();
}
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.声明Season类对象的属性:用pricate 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;
}
// 5.其他诉求2:提供toString()
// @Override
// public String toString() {
// return "Season1{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
// 所有的Season类的对象重写这个方法都得到这句话,但是我想要让每个对象重写这个方法输出的
// 的语句或别的东西不一样,就让每个对象都重写这个shw(),怎么做呢?
// 在每个对象后加上{},在{}里面重写show()
// @Override
// public void show() {
// System.out.println("这是一个季节!");
// }
}
4、使用Enum定义枚举类的常用方法
public static void main(String[] args) {
// toString()
Season1 spring = Season1.SPRING;
System.out.println(spring.toString()); //SPRING
// 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]);
values[i].show();
}
System.out.println("****************");
//valueOf(String objName):返回对象名为objName的对象
// 如果没有名为objName的对象则抛出异常:java.lang.IllegalArgumentException
// Season1 summer = Season1.valueOf("SUMMER1");
Season1 summer = Season1.valueOf("SUMMER");
System.out.println(summer);
summer.show();
}
2、注解的使用
1、注解的理解
-
① jdk 5.0 新增的功能
-
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
-
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android
中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
代码和XML配置等。
2、如何自定义注解
public @interface MyAnnotationTest {
String value();
}
如何自定义注解:参照@SuppressWarnings定义
-
① 注解声明为:@interface
-
② 内部定义成员,通常使用value表示
-
③ 可以指定成员的默认值,使用default定义
-
④ 如果自定义注解没有成员,表明是一个标识作用。
说明:
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target4、jdk 提供的4种元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
出现的频率较低
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具有继承性。
四、Java集合
1、数组与集合
(1)集合与数组存储数据概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
(2)数组存储的特点
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
(3)数组存储数据的弊端
一旦初始化以后,其长度就不可修改。
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
(4)集合存储数据的优点
解决数组存储数据的弊端
2、Collection接口
-
|----Collection接口:单列集合,用来存储一个一个的对象
- |----List接口:存储有序的、可重复的数据。 -->“动态”数组
- |----ArrayList、LinkedList、Vector
- |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
- |----HashSet、LinkedHashSet、TreeSet
- |----List接口:存储有序的、可重复的数据。 -->“动态”数组
-
|----Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)
- |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
面试题:ArryList、LinkedList、vector三者的相同点与不同点?
ArrayLIst:线程不安全的;vector:线程安全的
2.1、Collection的常用方法()
public class CollectionTest { @Test public void test1() { //add():将元素添加到集合coll中 Collection coll = new ArrayList(); coll.add("123"); coll.add("ABC"); coll.add(456); coll.add(new Date()); //size():获取添加的元素的个数 System.out.println("coll开始长度为:" + coll.size()); //addAll():将一个集合的元素添加到另一个集合中 Collection coll1 = new ArrayList(); coll1.add("CC"); coll1.add("turtle"); coll.addAll(coll1); System.out.println("coll添加了coll1之后的长度为:" + coll.size()); System.out.println(coll); // clear():清空集合元 素 coll.clear(); //isEmpty():判断集合是否为空 boolean empty = coll.isEmpty(); System.out.println(empty); } @Test public void test2() { // Contains(Object obj):判断当前集合中是否包含obj // 我们在判断时会调用obj所在类对象的equals() --- 重写equals() Collection coll = new ArrayList(); coll.add(123); coll.add(new String("master")); coll.add(new Person("Tom", 20)); boolean master = coll.contains("master"); System.out.println(master); // ContainsAll(Collection coll):判断形参中coll的元素是否全部存在于当前集合中 Collection coll1 = new ArrayList(); coll1.add(123); coll1.add("master"); boolean b = coll.containsAll(coll1); System.out.println("************"); System.out.println(b); System.out.println(coll1); } @Test public void test3() { // remove(Object obj):从当前集合中删除元素obj Collection coll = new ArrayList(); coll.add(123); coll.add(new String("master")); coll.add(new Person("Tom", 20)); boolean master = coll.contains("master"); coll.remove("master"); System.out.println(coll);// [123, Person{name='Tom', age=20}] // removeAll(Collection coll1):差集,当前集合中移除coll1中的所有元素 Collection coll1 = Arrays.asList(123, 110); coll.removeAll(coll1); System.out.println(coll); } @Test public void test4() { // retainAll(Collection coll1):交集,获取当前集合和coll1集合的交集 Collection coll = new ArrayList(); coll.add(123); coll.add(new String("master")); coll.add(new Person("Tom", 20)); Collection coll1 = Arrays.asList(123, 110); coll.retainAll(coll1); System.out.println(coll); // equals(Object coll2):coll2和coll3的值和值的顺序一样才能得到true,因为是有序的 Collection coll2 = new ArrayList(); coll2.add(123); coll2.add(new String("master")); coll2.add(new Person("Tom", 20)); Collection coll3 = new ArrayList(); coll3.add(123); coll3.add(new String("master")); coll3.add(new Person("Tom", 20)); boolean equals = coll2.equals(coll3); System.out.println("---------------"); System.out.println(equals); } @Test public void test5() { //hashCode():返回当前对象的哈希值 Collection coll = new ArrayList(); coll.add(123); coll.add(new String("master")); coll.add(new Person("Tom", 20)); int i = coll.hashCode(); System.out.println(i); } @Test public void test6() { // toArray():将集合转换为数组 Collection coll = new ArrayList(); coll.add(123); coll.add(new String("master")); coll.add(new Person("Tom", 20)); Object[] objects = coll.toArray(); for (int i = 0; i < objects.length; i++) { System.out.println(objects[i]); } } @Test public void test7() { // asList():将数组转换为集合 List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC", "MM"}); System.out.println(list); } }
2.2 遍历collection的两种方式
-
使用iterator进行遍历
代码实现:
Collection coll = new ArrayList(); coll.add(12); coll.add("12"); coll.add(23); Iterator iterator = coll.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
-
使用forEach(底层仍然调用了迭代器)进行遍历
Collection coll = new ArrayList(); coll.add(12); coll.add("12"); coll.add(23); Iterator iterator = coll.iterator(); coll.forEach(System.out::println);
迭代器图解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8KG6WmF-1649144014089)(C:\Users\HaSee\AppData\Roaming\Typora\typora-user-images\image-20210909111207409.png)]
2.3、List接口
1、存储数据的特点
- List接口框架
- |----Collection接口:单列集合,用来存储一个一个的对象
- |----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
- |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
2、常用方法
增:add(Object obj)
删:remove(Object obj) / remove(int index);
改:set(index , Object ele)
查:get(index)
插:add(int index , Object ele)
长度:size()
遍历:①Iterator迭代器
②增强for循环
③普通分for循环
3、常用实现类
4、源码分析
- ArrayList的源码分析:
2.1 jdk 7情况下
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)
2.2 jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
…
后续的添加和扩容操作与jdk 7 无异。
2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
- LinkedList的源码分析:
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
4.Vector的源码分析:
- jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
- 在扩容方面,默认扩容为原来的数组长度的2倍。
5、存储的元素的要求
添加的对象所在的类,必须要重写equals()方法
面试题:
- 面试题:ArrayList、LinkedList、Vector三者的异同?
- 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
- 不同:见上
2.4、set接口
1、存储数据的特点
无序的、不可重复的
2、元素添加的过程
一、set:存储无序的,可重复的数据
1.无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值2.可重复性:保证添加的元素按照equals()比较时,不能返回true。即:相同的元素只能添加一个
二、添加元素的过程,以HashSet为例:
我们向Hashset中添加元素a,首先调用元素a所在类的HashCode方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在Hashset底层中的存放位置(即为索引位置),判断数组此索引位置上是否已经
有元素:
如果此位置上没有其他元素,则元素a添加成功 --> 情况1
如果此位置上有其他元素b(或者以链表的形式存在多个元素),则比较元素a和元素b的哈希值
如果元素a和元素b的哈希值不相同,则元素a添加成功 --> 情况2
如果哈希值相同,进而需要调用元素a所在类的equals()方法:
返回true,元素a添加失败
返回false,元素a添加成功 —> 情况3
对于情况2和3添加成功的情况而言:元素a与已经存在指定索引位置上的数据以链表的方式存储。
jdk 7 : 元素a放到数组中,指向原来的元素,而原来的元素就被往下挤,新添加的元素就相对于往上添加
jdk 8 : 原来的元素在数组中,指向元素a
总结:七上八下 HashSet底层:数组+链表的结构。
3、Treeset的使用
1.添加的数据,要求是同类的对象
2.两种排序方式:①自然排序 --> 实现Comparable接口 ②定制排序 --> 实现Compareto接口
hashCode():返回当前对象的哈希值
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String(“master”));
coll.add(new Person(“Tom”, 20));
int i = coll.hashCode();
System.out.println(i);
}
@Test
public void test6() {
// toArray():将集合转换为数组
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("master"));
coll.add(new Person("Tom", 20));
Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
@Test
public void test7() {
// asList():将数组转换为集合
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC", "MM"});
System.out.println(list);
}
}
```
2.2 遍历collection的两种方式
-
使用iterator进行遍历
代码实现:
Collection coll = new ArrayList(); coll.add(12); coll.add("12"); coll.add(23); Iterator iterator = coll.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
-
使用forEach(底层仍然调用了迭代器)进行遍历
Collection coll = new ArrayList(); coll.add(12); coll.add("12"); coll.add(23); Iterator iterator = coll.iterator(); coll.forEach(System.out::println);
迭代器图解:
[外链图片转存中…(img-n8KG6WmF-1649144014089)]
2.3、List接口
1、存储数据的特点
- List接口框架
- |----Collection接口:单列集合,用来存储一个一个的对象
- |----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
- |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
2、常用方法
增:add(Object obj)
删:remove(Object obj) / remove(int index);
改:set(index , Object ele)
查:get(index)
插:add(int index , Object ele)
长度:size()
遍历:①Iterator迭代器
②增强for循环
③普通分for循环
3、常用实现类
4、源码分析
- ArrayList的源码分析:
2.1 jdk 7情况下
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)
2.2 jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
…
后续的添加和扩容操作与jdk 7 无异。
2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
- LinkedList的源码分析:
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
4.Vector的源码分析:
- jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
- 在扩容方面,默认扩容为原来的数组长度的2倍。
5、存储的元素的要求
添加的对象所在的类,必须要重写equals()方法
面试题:
- 面试题:ArrayList、LinkedList、Vector三者的异同?
- 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
- 不同:见上
2.4、set接口
1、存储数据的特点
无序的、不可重复的
2、元素添加的过程
一、set:存储无序的,可重复的数据
1.无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值2.可重复性:保证添加的元素按照equals()比较时,不能返回true。即:相同的元素只能添加一个
二、添加元素的过程,以HashSet为例:
我们向Hashset中添加元素a,首先调用元素a所在类的HashCode方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在Hashset底层中的存放位置(即为索引位置),判断数组此索引位置上是否已经
有元素:
如果此位置上没有其他元素,则元素a添加成功 --> 情况1
如果此位置上有其他元素b(或者以链表的形式存在多个元素),则比较元素a和元素b的哈希值
如果元素a和元素b的哈希值不相同,则元素a添加成功 --> 情况2
如果哈希值相同,进而需要调用元素a所在类的equals()方法:
返回true,元素a添加失败
返回false,元素a添加成功 —> 情况3
对于情况2和3添加成功的情况而言:元素a与已经存在指定索引位置上的数据以链表的方式存储。
jdk 7 : 元素a放到数组中,指向原来的元素,而原来的元素就被往下挤,新添加的元素就相对于往上添加
jdk 8 : 原来的元素在数组中,指向元素a
总结:七上八下 HashSet底层:数组+链表的结构。
3、Treeset的使用
1.添加的数据,要求是同类的对象
2.两种排序方式:①自然排序 --> 实现Comparable接口 ②定制排序 --> 实现Compareto接口
更多推荐
JavaSE
发布评论