Java学习笔记

第一章 java基础

标识符的命名规则

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
32
33
/ /T0D0标识符得命名规则

//1.英文拉丁字母

/ / wx , qq

String username = "zhangsan";

String xingming = "lisi";

// 2.符号

//@,#,%,$

//3.标识符只能采用下划线和美元$符号,其他符号不能使用,称之为特殊符号

//4.空格属于特殊符号,所以也不能作为标识符使用

String _name = "wangwu";
String $name = "wangwu" ;
String $na_me = "wangwu";
system.out.println(_name);
system.out.println($name);
system.out.println($na_me);
//5.阿拉伯数据0到9可以作为标识符使用,但是不能开头
//数字如果在标识符得开头位置,那么会被识别为数字,而不是标识符,所以会发生错误
//String 1name = "zhangsan";
String name1 = "zhangsan";
//6.标识符区分大小写
String Public = "zhaoliu";
//7.驼峰命名法
String userName = "小李";

数据存储单位

1.比特(bit位):数据运算的最小存储单位。

2.字节(byte):数据的最小存储单位。

字节和比特之间可以互相转换的,关系如下

1byte = 8bit

数据类型

基础数据类型

一共4类8种,分别如下

  1. 整数数据类型:byte,short,int,long分别是8位,16位,32位,64位,整型常量类型默认为int类型

  2. 浮点类型:含有小数点的数据类型;float(单精度浮点类型),double(双精度浮点类型)。,浮点型常量默认为double类型

    注意:如果float定义时,没有加f则会被认为在定义double类型,从而出现类型转换的错误。

    1
    2
    3
    //单精度浮点类型,我们需要用F(f)来结尾如
    float f = 2.0f;
    double d = 12.3;
  3. 字符类型:char类型代表一个单一的字符,进行运算时会被转为ACSII码进行运算,用单引号定义。

    1
    char c = '@';
  4. 布尔类型:boolean类型:判断条件是否成立,成立取值true,不成立取值false,该类型不能做运算,可以与字符串类型进行拼接。

    1
    boolean bln  = true;

引用数据类型

引用数据类型:类 数组 接口 枚举 注解

就比如最常用的String类型就是引用类型。String可以用来声明包含0个,1个,等多个字符的字符串;

自动类型提升

规则:当容量小的变量与容量大的变量做运算时,结果自动转换为容量大的数据类型。(这里的容量指表示数据的范围。)

下面这条线,从左到右是自动类型提升,从右往左是强制类型转换

byte、short、char ——> int ——-> long ——> float ——->double

特别的:byte、short、char类型的变量直接做运算,结果为int类型。

强制数据类型转换

在java中范围小的数据可以直接转换为范围大的数据,但是范围大的数据不能转换成范围小的数据,如果非要大类型转小类型,那么我们应该使用小括号进行强制类型转换

1
2
3
4
byte b =10;
int i = s;
//在()中指明要转换的类型。
byte i1 =(byte)i;

运算符

就是参与数据运算的符号,Java定义,无法自行定义

算数运算符

  • 二元运算符:两个元素参与运算的运算符 1 + 2

  • 算术表达式 = 元素1 二元运算符 元素2 (表达式是有结果的,就需要有类型,这里的类型是元素种类类型最大的那一种,最小使用的类型为int类型)

    1
    2
    3
    4
    5
    6
    System.out.println(1 + 2) //3
    System.out.println(2 - 2) //0
    System.out.println(1 * 2) //2
    System.out.println(4 / 2) //2 (int,int) ==>int结果类型取决于类型大的那个
    System.out.println(1.0 / 2) //0.5 (double,int) ==>double,结果类型取决于类型大的那个
    System.out.println(5 % 2) //1(取余数,模运算)
  • 一元运算符:一个元素参与的运算符(++--)

    ++:本质是加1操作的简化版本,也成为自增运算符,注意,++i,表示i先自加1随后在进入下面的运算,而i++表示i先参加运算后再加1

    --:本质是减1操作的简化版本,也成为自减运算符,注意,–i,表示i先自减少1随后在进入下面的运算,而i–表示i先参加运算后再减1

赋值运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OperatorDemo3 {
public static void main(String[] args) {
// TODO 赋值运算符
//等号就是赋值运算符,将右边表达式的结果赋值给等号左边变量
//赋值运算符要考虑类型的关系
String name = "zhangsan";
//TODO 复合赋值运算符:+=
// 如果元素进行运算后重新赋值给自己,那么可以将运算和赋值的符号进行简化
// 复合赋值运算符进行运算时,数据类型不会发生变化
int i =0;
i+=1;//相当于i = i+1
System.out.println(i);

byte num1 = 10;
//下面这种写法由于二元运算符最小类型为int会导致报错
//num1 = num1 + 20;
//使用复合运算符就不会报错了,因为复合赋值运算符进行运算时,数据类型不会发生变化
num1 +=20;
System.out.println(num1);
}
}

逻辑运算符和位运算符

逻辑运算符用于组合条件表达式,并基于这些条件表达式的真假值来决定最终的结果。

&& (逻辑与) - “且”的关系

  • 描述: 当两个操作数都为 true 时,结果才为 true该运算符表达”且”的关系

  • 特点:

    • 短路行为: 如果第一个操作数为 false,则不会评估第二个操作数。
  • 示例:

    1
    2
    3
    boolean a = true;
    boolean b = false;
    boolean result = a && b; // 结果为 false

&(按位与) - “且”的关系

  • 描述:按位与 (&) 用于整数类型(如 byteshortintlong),它按位进行操作。对于两个整数,按位与运算符会比较它们每一位上的二进制位,如果两个位都为 1,那么结果位也为 1;否则结果位为 0

  • 特点

    • 当两个对应位都是 1 时,结果位为 1;否则结果位为 0
    • 没有短路行为。
  • 示例:

    1
    2
    3
    int a = 5;   // 二进制表示: 0101
    int b = 3; // 二进制表示: 0011
    int result = a & b; // 结果为 1 (二进制: 0001)

|| (逻辑或) - “或”的关系

  • 描述: 逻辑或 (||) 用于连接两个布尔表达式,并且只要这两个表达式中有任何一个为 true,整个表达式的结果就为 true

  • 特点:|| 具有短路行为,这意味着如果第一个表达式为 true,那么第二个表达式将不会被计算。

  • 示例:

    1
    2
    3
    boolean a = true;
    boolean b = false;
    boolean result = a || b; // 结果为 true

|(按位或) - “或”的关系

  • 描述:按位或 (|) 用于整数类型(如 byteshortintlong),它按位进行操作。对于两个整数,按位或运算符会比较它们每一位上的二进制位,如果两个位中至少有一个为 1,那么结果位也为 1;否则结果位为 0

  • 特点:

    • 当两个对应位中至少有一个为 1 时,结果位为 1;否则结果位为 0
    • 没有短路行为。
  • 示例

    1
    2
    3
    int a = 5;   // 二进制表示: 0101
    int b = 3; // 二进制表示: 0011
    int result = a | b; // 结果为 7 (二进制: 0111)

! (逻辑非)

  • 描述: 对操作数的布尔值取反。

  • 示例:

    1
    2
    boolean a = true;
    boolean result = !a; // 结果为 false

位运算符

位运算符在Java中用于按位操作整数类型的数据(如 byteshortintlong)。位运算符允许你直接对这些数据类型的二进制位进行操作。

1. & (按位与)

  • 当两个对应位都是 1 时,结果位才是 1;否则结果位为 0

2. | (按位或)

  • 当两个对应位中至少有一个是 1 时,结果位为 1;否则结果位为 0

3. ^ (按位异或)

  • 当两个对应位不同时,结果位为 1;相同则结果位为 0

4. ~ (按位取反)

  • 对每个位进行取反操作,1 变为 00 变为 1

5. << (左移)

  • 将二进制位向左移动指定的位置数,高位丢失,低位补零。

6. >> (带符号右移)

  • 将二进制位向右移动指定的位置数,高位补符号位(如果是负数,则补 1;如果是正数,则补 0)。

7. >>> (无符号右移)

  • 将二进制位向右移动指定的位置数,高位补 0

三元运算符 - 条件运算符

语法:

变量 = (条件表达式)?(任意表达式1):(任意表达式2)

1
2
3
4
5
6
7
8
9
10
11
public class OperatorDemo6 {
public static void main(String[] args) {
// TODO 运算符 - 三元运算符
// 指三个元素参与运算的运算符
//基本语法结构:变量 = (条件表达式)?(任意表达式1):(任意表达式2)
// 当条件表达式的结果为ture则执行表达式1,反之执行表达式2
int i = 10 ;
int k =(i == 5)? (i+10):(i+1);
System.out.println(k);
}
}

比较运算符和比较方法

  • ==是一个比较运算符

    • ==:既可以判断基本数据类型,也可以判断引用数据类型。
    • ==:如果判断基本数据类型,比较的是值是否相等,如果判断的是引用数据类型,则判断的是地址值是否相等
  • equals是Object类中的一个方法

    • 只能判断引用类型
    • 默认判断的是地址是否相等,但是使用中通常会在子类中重写该方法,用来判断内容是否相等

流程控制

顺序执行

  • 按照代码顺序从前往后执行

分支执行

  • if……else……
  • if……elif……….elif……..else……..
  • switch(变量){ case1: ……. ; case2: ……… ;default: ………..;},switch分支判断也叫等值分支判断。

其中在switch分支中如果想要跳出某个case并结束分支执行,就在那个case块的内容最后加上break用来跳出

重复执行(循环)

在Java中,循环结构允许你重复执行一段代码直到满足某个特定条件。Java提供了几种不同类型的循环结构,包括for循环、while循环和do-while循环。下面是每种循环的基本语法和示例。

for 循环

for循环是最常用的循环结构之一,特别适合已知循环次数的情况。

语法
1
2
3
for (初始化; 终止条件; 更新表达式) {
// 循环体
}

例子:

1
2
3
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}

这段代码将会输出数字1到5。

while 循环

while循环会在每次迭代前检查条件是否为真。只有当条件为真时,循环体才会被执行。

语法
1
2
3
while (条件) {
// 循环体
}

例子:

1
2
3
4
5
int i = 1;
while (i <= 5) {
System.out.println(i);
i++;
}

这段代码同样会输出数字1到5。

do-while 循环

do-while循环至少会执行一次循环体,然后在每次迭代后检查条件是否为真。

语法
1
2
3
do {
// 循环体
} while (条件);

例子:

1
2
3
4
5
int i = 1;
do {
System.out.println(i);
i++;
} while (i <= 5);

这段代码同样会输出数字1到5。

控制语句

在循环内部,你可以使用控制语句来改变循环的执行流程:

  • break: 用于立即退出循环。
  • continue: 用于跳过当前迭代,直接进入下一次迭代。
  • return: 用于从方法中返回并退出循环。

例子:

1
2
3
4
5
6
7
8
9
for (int i = 1; i <= 10; i++) {
if (i == 5) {
continue; // 跳过数字5
}
if (i == 8) {
break; // 在数字8时退出循环
}
System.out.println(i);
}

这段代码将输出1到4和6到7。

嵌套循环

循环还可以嵌套在一起,即在一个循环内包含另一个循环。

示例:

1
2
3
4
5
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
System.out.println("i: " + i + ", j: " + j);
}
}

这段代码会输出一个3x3的矩阵,每个元素都表示两个循环变量的值。

小练习:九层妖塔

请在控制台输出打印如下

image-20240414160246249

代码如下:

1
2
3
4
5
6
7
8
9
10
11
for(int i = 0;i<9;i++){
//完成打印但是打印出来的是全部对齐的,并未达到要求
//下面打印空格因为最后一行不用输出空格所以我们只打印8层就可以,按照规律每层打印的空格数=8-层号
for(int j=0;j<8-i;j++){
System.out.print(" ");
}
for(int j=0;j<i*2+1;j++){
System.out.print("*");
}
System.out.println(" ");
}

image-20240414161235302

switch-case

在Java中,switch 语句是一种选择结构,用于基于不同的条件来执行不同的代码块。它提供了一个更简洁的方式来编写多分支的 if-else 结构。

基本语法

1
2
3
4
5
6
7
8
9
10
11
switch (expression) {
case constant1:
// 执行相应的代码块
break;
case constant2:
// 执行相应的代码块
break;
// 其他 case 分支
default:
// 如果没有匹配到任何 case,则执行此代码块
}

注意:

  1. 表达式 (expression) 必须是一个整数、字符、枚举或字符串。
  2. 常量 (constant) 必须是字面量或者常量表达式(不能是变量)。
  3. break 语句用来结束当前的 case 并跳出整个 switch 语句。如果省略 break,程序会继续执行下一个 case,这就是所谓的“穿透”行为。
  4. default 子句是可选的,当 expression 的值与所有 case 常量都不匹配时,会执行 default 中的代码。default 可以放在 switch 语句的任何位置,但是通常放在最后。

示例

假设我们有一个变量 day,表示一周中的某一天,我们可以使用 switch 语句来输出这一天的名称:

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
int day = 4;  // 假设今天是星期三
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default:
System.out.println("Invalid day");
}

Java 7 和以后版本的新特性

从Java 7开始,switch 表达式可以接受 String 类型。此外,从Java 12开始,switch 语句支持新的箭头语法,使得代码更加简洁。

Java 12+ 的增强版 switch 表达式

1
2
3
4
5
6
7
String fruit = "apple";
var result = switch (fruit) {
case "apple" -> "You chose an apple!";
case "banana" -> "You chose a banana!";
default -> "Unknown fruit";
};
System.out.println(result);

这种形式允许你将 switch 语句当作一个表达式来使用,并且不需要 break 语句。

转义字符(常用,复习)

在Java中,转义字符是一种特殊的字符序列,用于表示一些不可见或者特殊含义的字符。这些字符通常以反斜杠(\)开始。下面是一些常用的Java转义字符及其含义:

  1. \n - 新行(换行符)
  2. \t - 水平制表符(Tab键)
  3. \b - 退格(Backspace)
  4. \f - 换页符
  5. \r - 回车(将光标移至行首)
  6. \' - 单引号
  7. \" - 双引号
  8. \\ - 反斜杠
  9. \xxx - 八进制转义序列,其中xxx是1到3位的八进制数,代表对应的ASCII字符。
  10. \uxxxx - Unicode转义序列,其中xxxx是四位的十六进制数,代表对应的Unicode字符。

例如,你可以使用这些转义字符来构造字符串:

1
String example = "Hello,\n\tWorld!"; // Hello,后面是一个新行,然后是制表符,接着是World!

在这个例子中,\n 代表一个新行,\t 代表一个水平制表符。

你还可以使用这些转义字符来创建包含特殊字符的字符串,比如:

1
String withQuote = "He said, \"Hello, World!\""; // 字符串中含有双引号

在这个例子中,\" 用来表示字符串中的双引号。

键盘输入

目的:为了拿到用户输入的内容

步骤:

  • 导入该类所在的包,java.util.*
  • 创建该类的对象
  • 利用对象调用里面的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package KeyInput;

import java.util.Scanner;

public class KeyInput {
public static void main(String[] args) {
//1.导入类,这一步是自动的
Scanner scanner = new Scanner(System.in);
//2.使用对象中的方法来接收用户输入
String userInput = scanner.next();
System.out.println(userInput);
}
}

常用方法如下:

    • String next() 查找并返回此扫描仪的下一个完整标记。
      String next(Pattern pattern) 返回下一个标记,如果它与指定的模式相匹配。
      String next(String pattern) 返回下一个标记,如果它与从指定字符串构造的模式相匹配。
      BigDecimal nextBigDecimal() 扫描输入下一个标记为 BigDecimal
      BigInteger nextBigInteger() 扫描输入下一个标记为 BigInteger
      BigInteger nextBigInteger(int radix) 扫描输入下一个标记为 BigInteger
      boolean nextBoolean() 扫描输入的下一个令牌到一个布尔值,并返回该值。
      byte nextByte() 扫描输入下一个标记为 byte
      byte nextByte(int radix) 扫描输入下一个标记为 byte
      double nextDouble() 扫描输入下一个标记为 double
      float nextFloat() 扫描输入下一个标记为 float
      int nextInt() 扫描输入下一个标记为 int
      int nextInt(int radix) 扫描输入下一个标记为 int
      String nextLine() 将此扫描仪扫描到当前行,并返回跳过的输入。

进制转换

当我们讨论“四种进制”时,通常指的是二进制、八进制、十进制和十六进制。这些进制在计算机科学和数字电子学中非常常见。下面是一些关于这些进制之间相互转换的基本知识点和技巧。

1. 二进制(Binary)

  • 定义:二进制是一种基于2的数制系统,只包含两个数字:0 和 1。
  • 转换到十进制:每个二进制位乘以其位置对应的2的幂次,然后求和。例如,二进制数1011转换成十进制为1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 8 + 0 + 2 + 1 = 11

2. 十进制(Decimal)

  • 定义:十进制是我们日常生活中最常用的数制,它包含了0到9这十个数字。
  • 转换到二进制:通过不断地用2去除十进制数,并记录每次的余数,直到商为0,然后将所有余数倒序排列即得二进制数。例如,十进制数11转换成二进制的过程为11 / 2 = 5 ... 1 -> 5 / 2 = 2 ... 1 -> 2 / 2 = 1 ... 0 -> 1 / 2 = 0 ... 1,因此得到二进制数1011

3. 八进制(Octal)

  • 定义:八进制是一种基于8的数制系统,使用0到7这八个数字。
  • 转换到二进制:每位八进制数可以转换为三个二进制位。例如,八进制数127转换成二进制为001 010 111
  • 转换到十进制:每位八进制数乘以其位置对应的8的幂次,然后求和。例如,八进制数127转换成十进制为1*8^2 + 2*8^1 + 7*8^0 = 64 + 16 + 7 = 87

4. 十六进制(Hexadecimal)

  • 定义:十六进制是一种基于16的数制系统,使用0到9这十个数字和A到F这六个字母(代表10到15)。
  • 转换到二进制:每位十六进制数可以转换为四个二进制位。例如,十六进制数FA转换成二进制为1111 1010
  • 转换到十进制:每位十六进制数乘以其位置对应的16的幂次,然后求和。例如,十六进制数FA转换成十进制为15*16^1 + 10*16^0 = 240 + 10 = 250

进制之间的直接转换

  • 二进制与八进制:二进制数每三位一组转换为八进制数的一位,反之亦然。
  • 二进制与十六进制:二进制数每四位一组转换为十六进制数的一位,反之亦然。

原码、补码、反码

  1. 二进制的最高位是符号位:0表示正数,1表示负数
  2. 正数的原码补码反码都一样,简称正数三码合一
  3. 负数的反码 = 符号位不变,其他位取反
  4. 负数的补码 = 反码 + 1,负数的反码 = 负数的补码 - 1
  5. 0的反码,补码都是0
  6. java没有无符号数,换言之,java中的数都是有符号的
  7. 在计算机运算时,都是以补码的方式来运算的
  8. 当我们看运算结果的时候,要看他的原码

第二章 面向对象

什么是面向对象?

所谓面向对象,其实就是分析问题时,以问题所涉及的是或者物为中心的分析方式

类和对象

类 and 对象(先有类,再有对象)

类表示归纳和整理;对象表示具体的事务。

在Java中我们用class表示类,通常类名首写字母大写

基本语法结构:

1
2
3
4
5
6
7
class 类名{

特征(属性)

功能(方法)

}

创建对象的语法结构

对象:类的实例化(具象化)

1
2
new 类名();//小括号不能丢
//new是关键字,表示创建一个具体的对象,使用一次,创建一次,每次都是全新的对象。

对象是将内存地址赋值给了变量,所以变量其实引用了内存中的对象,所以称之为引用变量,而变量的类型称之为引用数据类型。

特殊的对象:空对象(null),没有引用的对象,称之为空对象,关键字对象,所有引用对象的类型默认取值为null

属性

所谓属性,就是类的对象的相同特征

声明:属性类型 属性名称 = 属性值

如果在声明属性的同时进行了初始化赋值,那么所有对象的属性就完全相同。如果希望每个对象的属性不一致,那么我们可以只声明即可,那么属性会在构造对象的时候默认初始化,而默认初始化的值取决于属性的类型

byte,short,int,long ==> 0

float,double ==> 0.0

boolean flg ==> false

char ==>空字符

引用数据类型 ==> null

1
2
3
4
//初始化并赋值zhansan
String name = "zhansan";
//只声明
String name;

对象在内存中存放的形式

image-20241022194827870

对象的内存分配机制

image-20241022200501634

类和对象的内存分配

image-20241022200428498

静态(static)

针对于具体对象的属性称之为对象属性,成员属性,实例属性

针对于具体对象的方法称之为对象方法,成员方法,实例方法

把和对象无关,只和类相关的称之为静态

和类相关的方法称为静态方法

和类相关的属性称为静态属性

静态语法:就是在属性和方法前面增加static关键字

在Java语法中:

  • 成员方法可以访问静态属性和静态方法

  • 静态方法不可以访问成员属性和静态方法

  • 在访问静态类的属性时,可以用静态类名.属性名直接访问。

静态代码块

当类的信息加载完成后,会自动调用静态代码块,可以完成静态属性的初始化功能

1
2
3
static{
//代码块内容
}

在我们构建对象时,代码块会进行初始化,但不是静态的

1
2
3
4
{
//代码块内容

}

方法

方法的声明

1
访问修饰符 【方法返回值类型】/【void】 方法名(参数){功能代码}

方法中void是空的意思,意味着没有结果。如果在方法中的结果需要返回,则确定返回值的类型利用return关键字来返回结果。没有返回值就用void

注意:

  • 一个方法可以最多有一个返回值,可以通过返回数组来获得多个返回值(获取多个返回值)
  • 方法的返回值可以是任意类型,包含基本数据类型或引用数据类型(数组,对象)
  • 如果方法要求有返回数据类型,则方法中最后执行的语句必须为return 值;
  • 如果方法使用了void修饰,则可以无返回值,也可以只写return
  • 返回值的类型存在自动类型提升[精度大的返回值类型可以返回一个小精度的,反之则不行],也就是说如果某个方法的返回值是double类型,那么返回int类型是符合语法的,反之则不行
1
2
3
4
5
6
//返回的结果是布尔类型的所以用Boolean
boolean register(){
System.out.println("注册成功")
//返回结果
return true;
}

方法传参(方法参数)

1.语法:方法名(参数类型 参数名称)

方法名(参数类型1 参数名称1,参数类型2 参数名称2,参数类型3 参数名称3) { }进行多个参数的传递。

2.传参注意事项:

  • 参数个数要匹配

  • 参数类型要匹配或兼容,精度小的可以传递给精度大的,反之则不行。

  • 参数顺序要匹配

  • 当参数个数不确定时,我们可以使用特殊语法来声明:可变参数

    1
    2
    //参数类型... 参数名称
    方法名(String ... name){ }
  • 当参数列表中还有别的参数时,我们需要将可变参数声明在参数列表最后。也就是说,可变参数是函数中最后一个参数

  • 基本数据类型:传数值,也叫值拷贝,不会影响主方法中的变量。

  • 引用数据类型(对象,数组):传引用地址,这就会影响主方法中的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//main方法中
User user = new User();
String name ="zhangsan"
//传递参数
user.sayHello(name);



class User{
void sayHello(String name){
//使用参数
System.out.println("hello"+ name);
}
}

方法调用细节

  1. 同一个类中的方法调用:直接调用即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package method;

public class Method {
public static void main(String[] args) {
A a = new A();
a.sayOk();
}
}

//print和sayOk在同一个类中,可直接调用
class A {
public void print(int n ){
System.out.println("打印了" + n+"次");
}

public void sayOk(){
print(10);
}
}

  1. 跨类中的方法调用:A类调用B类中的方法:需要通过对象名调用。
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
32
33
34
35
package method;

public class Method {
public static void main(String[] args) {
A a = new A();
a.sayOk();
a.m1();
}
}

//
class A {
public void print(int n ){
System.out.println("打印了" + n+"次");
}

public void sayOk(){
print(10);
}
//跨类调用:需通过对象名调用
public void m1(){
System.out.println("m1方法被调用了");
//创建一个B的对象
B b = new B();
//利用对象去调用
b.hi();
System.out.println("m1方法继续执行:)");
}
}

class B {
public void hi(){
System.out.println("B类中的hi方法被执行了");
}
}

方法的调用机制

[方法调用机制]: https://www.processon.com/diagraming/671795ce61fdee7d75e9306f “方法调用机制”

小插曲

Java中的三块内存区域

:存放的是(方法,变量)

:存放(对象)

元空间:存放类的信息,也称之为方法区

包中容纳类

语法:package 包完整路径;

包完整路径用·隔开

包名一般全是小写字母

包的导入:

我们用import语法来导入包。

import使用在package后class前

1
2
3
4
5
package xxx.xxx.Xxxx;
import xxxxxx;
public class xxxx{

}

面向对象——构造方法

基本介绍

构造方法又叫**构造器(constructor)**,是类的一种特殊方法,它的主要作用是完成对新对象的初始化。它有几个特点:

  1. 方法名和类名相同
  2. 没有返回值
  3. 在创建对象时,系统会自动调用该类的构造器完成对象初始化

构造方法:专门用于构建对象

基本语法如下

1
2
3
[修饰符] 方法名(类名) (形参列表){
方法体;
}

注意点

  • 构造方法也是方法,无返回值但不能使用void关键字
  • 方法名和类名完全相同
  • 一个类可以有多个构造方法,也可以进行重载(重载的概念:方法名相同,参数的类型,顺序,个数不同,且与返回值类型无关。)
  • 如果类中没有任何构造方法,那么JVM(java虚拟机)会自动添加一个公共的无参的构造方法。可以通过javap来进行反编译查看。
  • 如果类中有构造方法===>JVM不会提供默认构造方法
  • 一旦定义了构造器,那么就会覆盖默认的无参构造方法,除非显式的声明一下。
  • 构造方法也是方法,可以传递参数,但是一般传递参数的目的是用于对象属性的赋值

this关键字(重点)

什么是this?

java虚拟机会给每个对象分配this,代表当前对象。坦白的讲,要明白this不是一件容易事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class  Dog {
String name;
int age;

//构造器
// public Dog(String dName, int dAge) {
// name = dName;
// age = dAge;
// }

/*
为了提高代码可读性,如果能把dName改成name dAge改成age就好了,但是会出现
name = name; age = age的现象这样就导致了自身给自身赋值,那么怎么解决呢?这里引入this
*/
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public void printInfo(){
System.out.println(name + "\t"+ age+"\t");
}
}

总结:那个对象调用,this就代表那个对象。

this在JVM内存中的理解

简单理解:this在堆内存中指向了自己。

image-20241023192808378

this的使用细节

  • this关键字可以用来访问本类的属性、方法、构造器

  • this用于区分当前类的属性和局部变量

  • 访问成员方法的语法:this.方法名(参数列表)

  • 访问构造器语法:this(参数列表);注意,只能在构造器使用且该条语句必须位于第一条语句。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class User{
    //构造器1
    User(){
    //使用this关键字传入参数调用下面构造方法
    this("zhansan");
    }
    //构造器2
    User(String name){
    //接收name并用this关键字调用下面方法
    this("zhangsan","男");
    }
    //构造器3
    User(String name, String sex){
    System.out.println("名字是:"+name+","+sex);
    }
    }
  • this不能再类定义的外部使用,只能在类定义的方法中使用

supper关键字(重点)

super代表父类的引用,用于访问父类的属性、方法、构造器

基本介绍:

  • super可以访问父类的属性,但不能访问父类中被private修饰的属性和方法
    • 语法super.属性名/方法名
  • 访问父类的构造器
    • 语法super(参数列表)且只能放在构造器中并且位于构造器中的第一条语句位置。

面向对象——封装

封装指的是把抽象出来的属性和对属性的操作(方法)封装在一起,数据(属性)被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。

想想一个电视,它只对外部暴露了按钮,但是按钮具体内部时怎么操作的,我们不得而知

面向对象——继承

关键字:Extends

基本语法:

1
class 子类 extends 父类 { }

类存在父子关系:子类可以直接获取到父类的成员属性和成员方法

类的继承只能是单继承,一个类只能有一个父类,不能存在多个父类

在继承中,如果父类和子类含有相同的属性,那么可以采用特殊的关键字进行区分:super & this.

super:表示上一级

this:表示当前

继承中的细节

  1. 子类必须调用父类的构造器,完成父类的初始化
  2. 当创建子类对象时,不管使用子类的那个构造器默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类中使用super指明使用父类的那个构造器对父类进行初始化,否则编译不通过
  3. 如果希望指定调用父类某个构造器,就去使用super(参数列表)去指定调用
  4. super在使用时,需要放在构造器的第一行位置。
  5. super和this都需要在构造器第一行使用,因此这两个关键字不能存在同一个构造器中
  6. java中所有的类都是Object的子类

继承中的方法问题

构造方法(完成内存控件属性的初始化操作):

父类对象是在子类对象前创建完成的,创建子类对象前,会先创建父类对象

如果父类提供了有参构造方法,那么JVM就不会提供默认的构造方法,那么子类应该调用super方法(带入参数)构建父类对象。

继承在JVM中的分布

image-20241027205928446

面向对象——多态(难点)

所谓多态,其实就是一个对象在不同场景下表现出来的不同状态和形态。

多态语法其实就是对对象的使用场景进行了约束。

以父类声明的子类对象,只能使用父类中的方法,如果子类对象想使用子类方法,需要用子类来声明子类对象

多态的基本介绍

方法或对象具有多种形态,它是建立在封装和继承基础之上的。

多态的体现(重难点10.28日复习)

  • 方法的多态 - 方法的重载和方法的重写
  • 对象的多态(核心)
    1. 一个对象的编译类型运行类型可以不一致
    2. 编译类型在定义对象时,就确定了,不能改变。
    3. 运行类型可以变化
    4. 编译类型看定义时 =号 的左边,运行类型看 =号的右边。

比如Master master = new Dog(); 在这条语句中,他的编译类型是Master,而运行类型是Dog

  • 方法参数的多态

方法多态的向上转型(难点)

!!!!针对的是成员方法

本质:父类的引用指向了子类的对象,比如:Animal animal = new Dog()

语法:父类类型 引用名 = new 子类类型()

特点:编译类型看左边运行类型看右边。

①可以调用父类中的所有成员(注意访问权限)

②不能调用子类中特有成员,因为在编译阶段决定能调用那些成员方法是由编译类型决定的。

③在运行阶段,最终运行效果看子类的具体实现(也就是看运行类型),会先在子类中查找是否存在某个方法,并且权限可以访问到,如果有,则使用子类中的方法,如果没有,则在父类中去寻找。

方法多态的向下转型(难点)

类似于强制转换的规则:大的可以转换成小的,但小类型不能变为大类型。

语法:子类类型 引用 = (子类类型) 父类引用;

注意点:

①只能强制转换父类的引用,不能强制转换父类的对象

②要求父类的引用必须指向的是当前目标类型的对象

③当完成向下转型后,可以调用子类类型中所有的成员

属性

属性没有重写之说,属性的值只看编译类型,也就是等号左边。

属性也不存在动态绑定机制。

动态绑定机制(重点)

  • 当调用对象方法时,该方法会和对象的运行类型(也就是等右边的类型/内存地址)绑定。
  • 当调用对象属性时,不存在动态绑定机制,哪里声明就在哪里使用。

参数多态

方法定义的形参类型为父类类型,实参允许为子类类型。

面向对象——方法重载(overload)

相同的方法:在一个类中,相同方法是指方法名,参数列表相同,和返回值类型无关的方法。

方法重载: 在Java中,如果方法名称相同,但是参数列表(个数,顺序,类型)不相同,会认为是不同的方法,只是名称一样,我们称之为方法的重载。

  • 成员方法在被调用时,同一个方法名是通过参数的类型,个数进行区分的

  • 在构造方法中,想要调用其他构造方法,那么需要使用关键字:this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.itheima.面相对象综合实例;

public class ClassObject2 {
public static void main(String[] args) {
//我们只需要调用无参构造方法即可
//创建对象时会调用构造函数
User user = new User();

}
}
class User{
User(){
//使用this关键字传入参数调用下面构造方法
this("zhansan");
}
User(String name){
//接收name并用this关键字调用下面方法
this("zhangsan","男");
}
User(String name, String sex){
System.out.println("名字是:"+name+","+sex);
}
}

打印如下:

1
名字是:zhangsan,男

结论

基本数据类型:

基本数据类型匹配方法时,可以在数值不变的情况下扩大数据的精度。

byte数据类型无法和char类型做转换,char没有负数,而byte存在着负数

引用数据类型:

引用数据类型无法扩大数据精度,但是引用数据类型匹配方法时,会往object类去找。

比如A类是B类的父类,那么当调用的方法在B类中不符合时,就去A类中寻找,如果A类中没有就去A类的父类object中寻找

面向对象——方法重写(override)

定义:在父类中的方法其实主要体现通用性(可以被多个子类继承,可以被多个子类使用自身方法),无法在某一个特定情况去使用这个方法。如果子类对象需要再某个特定业务逻辑中使用该父类的方法,那么就需要对父类中的方法进行重写,这个操作成为重写。

请注意:

  • 子类中的方法不会覆盖父类的方法,只是父类当前方法不适用这个场景

  • 如果想在当前类调用父类方法可以使用super关键字进行调用

  • 当子类的方法中的返回值类型,参数列表,方法名称都和父类相同时才可以进行重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itheima.面相对象综合实例;

public class RewriteMethods {
public static void main(String[] args) {
Child child = new Child();
Parent parent =new Parent();
System.out.println(child.sum());//40
System.out.println(parent.sum());//20

}
}
class Parent{
int i = 10;
int sum(){
return i+10;
}
}
class Child extends Parent{
int i =20;
int sum(){
return i+20;
}
}

通过以上例子,总结四点:

  • 一个对象能使用什么方法,取决于引用变量的类型
  • 一个对象能使用什么属性,取决于引用变量的类型
  • 一个对象的方法具体使用(这个方法怎么去执行,逻辑是什么,直接使用还是间接使用),需要看具体对象
  • 一个对象的属性具体的使用是不需要看具体的对象的,属性在哪里声明就在哪里使用

方法重写的注意点

  • 子类方法的参数,方法名称,要和父类方法的参数,方法名称一致。
  • 子类方法的返回类型和父类方法返回类型一样,或者子类方法的返回值的类型是父类返回值类型的子类
    • 比如父类public Object getInfo(){},子类中public String getInfo(){}由于String是Object的子类,所以这也是方法的重写(这句话非常非常非常重要)
  • 子类方法不能缩小父类方法的访问权限。访问权限排序:public > protected > 默认 > private

面向对象——递归

定义:方法调用自身,成为递归方法

递归最经典的案例——求n以内的奇数和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.itheima.面相对象综合实例;
public class RewriteMethods {
public static void main(String[] args) {
int result = getSum(20);
System.out.println(result);
}

static int getSum(int n) {
//三元运算符,当n是偶数时会执行(n-1),当n是奇数时直接用n
n = n % 2 == 0 ? n - 1 : n;
if (n == 1) {
return 1;

} else {
return n + getSum(n - 2);
}
}
}

递归经典问题—阶乘

一个大于1的数的阶乘就是这个数乘以这个数减一的阶乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima.面相对象综合实例;
public class RewriteMethods {
public static void main(String[] args) {
int result1 = jieCheng(5);
System.out.println(result1);
}
//求n以内的数的阶乘
public static int jieCheng(int n){
if(n<= 1){
return 1;
}else {
return n * jieCheng(n-1);
}
}
}

注意:

  • 递归应该有跳出的逻辑
  • 调用自身时,传入的参数应该是有规律的

方法递归的执行过程图解

image-20241023120245495

面向对象——访问权限

  • public:公共的,访问权限修饰符

在Java中,公共类只有一个,且名称和源码文件相同

main方法:main方法是有JVM调用的,JVM可以任意调用,可以忽略权限问题

Java中的访问权限有四种:

  • private:私有的,同一个类中可以使用,别的类,其他地方无法使用
  • (default):默认权限,当不设定任何权限时,JVM会默认提供权限,本类,同包、可以访问,不同包下不可以访问,子类不可以访问
  • protected:受保护的权限,本类,同包、子类可以访问,不同包不可以访问。
  • public:公共的,任意使用。

image-20241027153644625

Object类

equals(Object obj)

  • 判断指定的对象是否与此对象“相等”。
  • 默认实现比较的是两个对象的引用是否相同(即是否指向同一个对象/引用地址是否相同),但在很多情况下需要重写此方法来实现基于内容的比较。

hashCode()方法

  • 提高具有哈希结构的容器的效率
  • 两个引用,如果指向了同一个对象,则哈希值肯定是一样的。
  • 两个引用,如果指向的是不同的对象,则哈希值是不一样的。
  • 哈希值主要是根据地址号来的,但是不能把哈希值等价于地址
  • 可以进行重写

toString()方法

  • 返回值:全类名 + @ + 哈希值的十六进制
  • 一般我们都会重写toString方法,用于返回对象的属性信息。
  • 当我们输出一个对象时,toString方法会被默认调用。
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
32
33
34
35
36
package Object.object_toString;

public class ToString {
public static void main(String[] args) {
test test = new test("巡山", 12, "小");
//因为子类中没有toString方法,所以这里调用的是父类中的toString方法
//当在子类中重写了toString方法时,这时 就会调用子类本身的toString
System.out.println(test.toString()); // Object.object_toString.test@b4c966a
//重写后输出的是:test{name='小', job='巡山', age=12}
}
}


class test{
private String name;
private String job;
private int age;

public test(String job, int age, String name) {
this.job = job;
this.age = age;
this.name = name;
}

//重写Object类中的toString
//默认输出对象的属性。 alt + insert ===>重写

@Override
public String toString() {
return "test{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", age=" + age +
'}';
}
}

面向对象高级—类变量和类方法

类变量(静态变量)

类变量也被成为静态变量,用static关键字修饰,是该类所有对象共享的变量,访问时遵循访问修饰规则。

  • static变量是同一个类所有对象共享。
  • static变量是在类加载的时候就生成了。

定义:访问修饰符 static 数据类型 变量名

访问类变量:

  1. 类名.静态变量
  2. 对象名.类变量名

使用场景

  1. 某个变量需共享时,可以设置为静态变量

与实例变量的区别

类变量是类的所有对象共享的,而实例变量是每个对象独享的。

类变量细节

  • 类变量可以通过类名.类变量名来访问;实例变量不能通过类名.变量名访问
  • 类变量在类加载的时候就完成初始化了,即使没有创建对象,也可以访问类变量
  • 类变量的生命周期是随着类加载开始,类卸载结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package OOP_Advanced.static_;

public class VisitStatic {
public static void main(String[] args) {
//访问语法:类名.类变量
//或者:对象名.类变量
System.out.println(A.name);
A a = new A();
System.out.println(a.name);

}
}

class A {
public static String name = "我是类变量name";
}

类方法(静态方法)

静态方法的基本概念

  1. 定义
    • 静态方法是属于类的,而不是类的某个特定对象。
    • 使用static关键字修饰的方法就是静态方法。
  2. 声明和调用
    • 静态方法可以在类的外部通过类名直接调用,无需创建类的实例。
    • 例如,如果有类MyClass包含一个静态方法myMethod,可以这样调用:MyClass.myMethod();

静态方法的特点

  1. 访问限制
    • 静态方法只能直接访问其他静态成员(包括变量和方法)。换句话说,静态方法无法访问实例成员
    • 如果需要访问非静态成员,则必须通过类的实例来访问。
  2. 生命周期
    • 静态方法随类的加载而加载,并且只加载一次。
    • 这意味着静态方法在程序启动时就已经存在了。
  3. 内存使用
    • 静态方法和静态变量存储在方法区中,所有对象共享这部分内存。
    • 因此,对于多个对象来说,静态方法占用的内存是共享的,这有助于节省内存。

静态方法的应用场景

  1. 工具方法
    • 常用于实现一些工具性质的方法,如数学计算、字符串处理等。
    • 例如,Java标准库中的Math类就提供了很多静态方法,如Math.abs()Math.sqrt()等。
  2. 工厂方法
    • 可以用来创建类的对象,特别是当对象的创建过程较为复杂时。
    • 例如,使用静态方法来返回一个配置好的对象实例,而不是让每个客户端都进行复杂的初始化工作。
  3. 单例模式
    • 在实现单例模式时,通常会提供一个静态方法来获取单例对象。
    • 如:Singleton.getInstance()

类方法的注意事项

  • 类方法中无this
  • 类方法可以通过类名.类方法 / 对象名.类方法
  • 普通方法与对象有关,必须通过对象名调用,不能通过类名调用

面相对象高级—main方法

1
2
3
public static void main(String[] args){

}

解释:

  1. java虚拟机需要调用类的main()方法,所以访问权限必须是public

  2. java虚拟机在执行main()方法时不必创建对象,所以方法必须是static

  3. 该方法接收String类型的数组参数,该数组中保存执行java命令时所传递给所有运行类的参数

  4. args这个字符串数组接收的参数就是 java 程序名 参数1 参数2 参数3,就指的是由参数1,2,3构成的数组。

    解释:java程序也可以通过javac 程序名 编译 ====> java 程序名 来运行一个java程序(在命令行中)。

面向对象高级—代码块

什么是代码块

  • 概念:

代码块又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

和方法不同的是,没有方法名,返回值,参数,只有方法体,而且不通过对象或类显示的调用,而是加载类时或创建对象时隐式调用。

  • 基本语法
1
2
3
4
5
6
7
[修饰符] {
//代码内容

};
static {
//代码内容
};
  • 注意点
    • 修饰符可写可不写,如果写只能写static
    • 代码块分为两类,使用static被称为静态代码块,没有static的被称为普通代码块,
    • 逻辑语句可以为任何逻辑语句
    • 结尾;可写可不写
    • 代码块调用的顺序优先于构造器。

代码块使用细节(重点,11.8复习)

  1. 静态代码块和代码块

    静态代码块会随着类的加载而执行,但静态代码块只执行一次;而普通代码块只有在进行对象创建的操作时(new 操作),才会被调用

  2. 类什么时候被加载?

    ①创建对象实例 —->new 操作

    ②创建子类对象实例 —->原因是创建子类对象实例时会先加载父类

    ③使用类的静态成员时(静态属性,静态方法)

  3. 普通的代码块,在创建对象实例时,会被隐式调用。每new一次,就会调用一次。

​ 但如果只是使用类的静态成员时,普通代码块并不会执行

  1. 创建一个对象时,在一个类中初始化的顺序是:先静态成员 —–> 普通成员 ——-> 构造方法

  2. 在调用构造器时,除了我们说的,构造器一开始会调用父类的无参构造器(这步是隐藏的,除非父类的构造器需要是重写过的带参构造器),此时,还会调用普通代码块。

    解释:在继承关系中,要注意,在初始化子类时,会先初始化父类,那么初始化父类在调用构造器时也会先super再普通代码块。

  3. 在继承关系中,当我们创建了子类对象时,个各类中的静态代码块/属性初始化;普通代码块/属性初始化;构造方法的调用顺序是什么样的呢?(面试题)

    1. 我有继承关系: B 继承 A

    2. 当我创建B类的对象时,执行顺序如下:

      类加载时:

      父类的静态代码块/属性 ——> 子类的静态代码块/属性 ——–>

      创建对象时:

      父类的普通代码块和/属性 ———> 父类的构造器 ——–> 子类的普通代码块/属性 ——-> 子类的构造器

面向对象高级—final关键字

final的用法

  • final可以修饰变量:变量的值一旦初始化不允许被修改

  • final可以修饰属性:但是JVM不会对自动对属性进行初始化,需要手动初始化,并且属性值不可以修改

  • final可以修饰方法:final修饰的方法不可以被子类重写

  • final可以修饰类:当final修饰一个类时,被修饰的类不可以有子类

  • final不可以修饰构造方法。

  • final可以修饰方法参数:一旦修饰,该参数无法修改。

一般将final修饰的变量称之为常量,或者叫不可变量。

final的使用场景

  • 当某个类不希望被继承时,可以用final修饰这个类。
  • 禁止重写父类中的某个方法,可以使用final来修饰这个方法。
  • 禁止修改类中的某个属性,可以使用final来修饰这个属性,类似与前端中使用const创建的变量。是一个常量。
  • 禁止修改某个局部变量,可以使用final修饰。

final细节

  • final修饰的被称为常量,一般用xxx_xxx_xxx来命名

  • final修饰的属性在定义时,必须赋初值,且以后不能修改,可以在下面的位置进行初始化

    定义时:public final double TAX_RATE = 0.08;

    在构造器中

    在代码块中

  • 如果final修饰的属性是静态的,则初始化的位置只能是:

    定义时/静态代码块中

  • final类不能继承,可以创建对象

  • final不能修饰构造器(即构造方法)

  • final往往和static搭配使用

    • 包装类(Interger,Double,Float,Boolean等都是final类),String类等都是final类

面向对象高级—抽象类(Abstract)

抽象类的定义

abstract修饰的类被称为抽象类,用abstract修饰的方法被称为抽象方法

  • 抽象类:不完整的类就是抽象类,抽象类不能够构造对象

    1
    2
    3
    4
    //语法
    abstract class 类名{

    }
  • 抽象方法:只有声明方法,而不去写方法里面的逻辑。

    1
    2
    //语法
    abstract 返回值类型 方法名(参数);
  • 在一个类中,有抽象方法就是抽象类,反之,如果有抽象类,他的方法不一定是抽象方法。

  • 抽象类无法直接构造对象,但是可以通过子类间接构造对象。

  • 如果抽象类中含有抽象方法,那么子类继承抽象类,需要重写抽象方法,将方法补充完整。

  • abstract关键字不能和final关键字一起使用

抽象类的细节

  1. 抽象类不能被实例化,也就是说抽象类不能创建对象
  2. 抽象类不一定要包含抽象方法
  3. 一旦类包含了抽象方法,那么这个类必须被声明为抽象类
  4. abstract只能修饰类和方法,不能修饰属性或其他内容
  5. 抽象类可以有任何成员,和普通类唯一的区别就是抽象类不能使用new关键字新建一个实例
  6. 抽象方法不能有函数体
  7. 如果一个类继承了抽象类,那么他必须实现(重写)抽象类中的所有抽象方法,除非自己也是抽象类。
  8. 抽象方法不能使用private、final、static来修饰,因为被这些关键字修饰的方法不能重写。

面向对象高级—接口(interface)(11.9复习)

接口的定义

接口:可以简单理解为规则。类比USB接口,接口它规定了一系列规则。接口就是给出一些没有实现的语法,封装到一起,到某个类要使用的时候在根据具体情况把这些方法重写。

基本语法:

1
2
//接口语法
interface 接口名称{ 接口属性,接口方法}
  • 接口其实是抽象的 —–> 可以省略abstract关键字
  • 接口中的方法可以是:
    • 抽象方法(不包含方法体)
    • 默认实现方法和静态方法(JDK8以及以后才有的特性)

接口的一段演示代码

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//接口 - 类似于USB
package OOP_Advanced.interface_;

public interface USB1 {//接口
//规定接口的相关方法
public void start();
public void stop();
}
package OOP_Advanced.interface_;

import java.nio.channels.Pipe;

//接口测试类
public class InterFace01 {
public static void main(String[] args) {
Computer computer = new Computer();
//创建手机、相机对象
Phone phone = new Phone();
Camera camera = new Camera();

//开始工作
computer.work(phone); //把手机接入到计算机

computer.work(camera);//把相机插入到计算机
}
}



//设备 - 手机
package OOP_Advanced.interface_;
//写一个手机类,为了实现USB接口需要去实现(implements)
//实现的话,必须实现接口中的方法
public class Phone implements USB1 {
@Override
public void start(){
System.out.println("开始工作");
}

@Override
public void stop(){
System.out.println("手机停止工作");
}
}

//设备 - 相机
package OOP_Advanced.interface_;

public class Camera implements USB1{
@Override
public void start() {
System.out.println("相机开始工作");
}

@Override
public void stop() {
System.out.println("相机停止工作");
}
}


//设备 - 电脑
package OOP_Advanced.interface_;

public class Computer {
//USB(接口)要插入电脑才能工作,所以这里编写一个方法
public void work(USB1 usb1){
//我们只需要传入不同的设备,就可以进行开始和结束的进行了
usb1.start();
usb1.stop();
}
}

接口的使用细节

  1. 接口不能被实例化,接口不能new操作
  2. 接口中的所有方法是public,接口中的抽象方法可以不用abstract修饰
  3. 一个普通类实现某个接口,就必须把接口中的方法都实现了,不可以少写或不写
  4. 抽象类实现接口可以不用实现接口的方法
  5. 一个类可以实现多个接口
  6. 接口中的属性,只能是final,也就是说,在接口中属性的修饰符只能是public static final 修饰符,
    1. 比如在接口中定义了一个变量:int n = 1 <==========>相当于这样写: public static final int n = 1;
    2. 接口访问属性的语法是:接口名.属性名(因为接口中的属性是静态属性)
  7. 接口不能继承其他类,但是接口可以继承接口
  8. 接口的修饰符只能是public和默认(不写),这一点和类是一样的。

继承和实现的区别

  1. 继承只能是单一继承,而接口可以一次性实现多个接口,所以实现接口更像是对继承单一机制进行的补充。
  2. 接口比继承更灵活,继承得满足猫(is - a) 动物,而接口只需要满足猫(like - a)兔子一样可以跑。

接口的多态特性(11.10复习)

  1. 接口的引用可以指向实现了接口的类的对象实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package OOP_Advanced.interface_;

    /*
    接口的多态体现
    1.接口的引用可以执行实现了接口的类的对象。
    */
    public class InterFace_poly {
    Interface_poly interfacePoly_1 = new Test_B();
    Interface_poly interfacePoly_2 = new Test_C();

    }

    interface Interface_poly {

    }
    class Test_B implements Interface_poly {}
    class Test_C implements Interface_poly {}
  2. 在数组上多态的体现,这一点和上面一样

    1
    2
    3
    4
    5
    6
    7
        Interface_poly[] interfacePolyArray = new Interface_poly[2];
    interfacePolyArray[0] = new Test_B();
    interfacePolyArray[1] = new Test_C();

    interface Interface_poly {}
    class Test_B implements Interface_poly {}
    class Test_C implements Interface_poly {}

接口的多态传递

接口A1和A2和类Teacher

  1. A1被Teacher类实现,那么 A1 a1 = new Teacher()一定是成立的,
  2. 但是如果这时写出了A2 a2 = new Teacher()就会报错,因为A2类并没有实现Teacher类
  3. 但如果A2继承了A1接口 A2 extends A1,那么错误就会消除,这个过程可以看做多态特性发生了传递。

面向对象高级—内部类(难点、重点)

内部类的定义

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),被嵌套的类称为(outer class)外部类

类的五大成员:属性、方法、构造器、内部类、代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package OOP_Advanced.innerclass;

public class InnerClass01 { //外部其他类

}

class OuterClass { // 外部类


//内部类
class InnerClass {

}
}

内部类的分类

共有4种内部类

  • 定义在 - 外部类局部 - 位置上 - 比如方法内:

    • 局部内部类(有类名)
      • 定义在方法或者作用域块中。
      • 可以直接访问外部类的所有成员,包括私有成员(private修饰)
      • 不能添加访问修饰符,可以被final修饰,一旦被final修饰就不能被继承了
      • 作用域:仅仅在定义它的方法或代码块中。
      • 访问局部内部类,可在外部类方法中创建内部 类的对象,即可访问。
      • 如果外部类变量和局部内部类变量重名,由于就近原则,如果此时想访问外部类的同名变量的话,语法是外部类名.this.变量名,而实际上外部类名.this就是外部类的对象,可以参考this的指向(谁调用this指向谁)
    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
    32
    33
    34
    package OOP_Advanced.innerclass;

    public class LocalInnerClass {
    public static void main(String[] args) {
    TestA testA = new TestA();
    testA.m2();
    }
    }


    class TestA {

    private int n1 = 4;
    private void m1(){
    System.out.println("我是TestA类中的方法m1");
    }

    public void m2(){
    //局部内部类 - 位置通常在方法中或者代码块中
    //这里定义的类被称为局部内部类
    class LInnerClass {

    private void InnerClass_m1(){
    System.out.println("TestA类中的变量n1 = " + n1);
    m1();
    }
    }

    //可以在这里使用局部内部类
    LInnerClass l = new LInnerClass();
    l.InnerClass_m1();
    }
    }

    • 匿名内部类(无类名,用的地方多)

  • 定义在 - 外部类成员 - 位置上:

    • 成员内部类(无static修饰)
    • 静态内部类(使用了static修饰)

面向对象高级—枚举(Enum)

定义:枚举是一个特殊的类,其中包含了一组特定的对象,一般使用大写的的字母,这些对象不会发生改变。

1
2
3
enum City{
BEIJING,SHANGHAI
}
  • 枚举使用enum关键字使用。
  • 枚举类的对象一般放在前面,如果还要写入别的语法那么应该用分号与后面的语法隔开。
  • 枚举类不能创建对象,只能在内部使用

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EnumTest {
public static void main(String[] args) {
System.out.print(City.SHANGHAI.name);
System.out.println(City.SHANGHAI.code);
System.out.print(City.BEIJING.name);
System.out.println(City.BEIJING.code);
}
}

enum City{
//相当于new一个对象,JVM完成
BEIJING("北京",1001),SHANGHAI("上海",1002);
City(String name, int code){
this.name = name;
this.code = code;
}
public String name;
public int code;
}

面向对象——匿名类

在某些场合下,类的名字不重要了,我们只是想使用类中的方法或功能,那么这个时候就需要用到特殊的语法:匿名类。

定义:没有名字的类,被称为匿名类

举个例子

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
public class NoNameClass {
public static void main(String[] args) {
//创建了一个SayHello的对象me
SayHello me = new SayHello();
//下面这种写法不需要再额外创建张三和李四的类,也就是我们创建了一个没有名字的类new person(){}
me.sayHelloToPeople(new Person() {
//重写抽象方法具体化
public String name(){
return "张三";
}
});
me.sayHelloToPeople(new Person() {
public String name(){
return "李四";
}
});
}
}

abstract class Person{
//有抽象方法所以是抽象类
public abstract String name();
}
class SayHello {
public void sayHelloToPeople(Person person) {

System.out.println("hello" + person.name());
}
}

接口也可以

1
2
3
4
5
6
7
8
9
10
11
12
13
//只关心你是怎么飞的,并没有考虑你是什么类,所以这里用匿名类。        
new Fly() {
public void fly() {
System.out.println("使用飞行器飞行");
}
}.fly();
}
}

//接口是抽象的
interface Fly{
public void fly();
}

面向对象——Bean规范

  • 类要求必须含有无参的,公共的构造方法
  • 属性必须私有化,然后提供公共的get,set方法

例子

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
public class Movie {
private int id;
private String name;
private double price;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}
}

面向对象——作用域

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.面相对象综合实例;

public class ZuoYongYu {
public static void main(String[] args) {
User2 user2 = new User2();
user2.test();
}

}
class Person2{

}

class User2 extends Person2{
String name = "lisi";

public void test(){
//如果属性和(局部)变量名称相同,访问时不加修饰符,那么优先访问变量
String name = "wangwu";//启动打印的是wangwu,如果注释掉这一行,那么打印的将会是lisi
System.out.println(name);
}
}

第三章 常见类和对象

常见类和对象——Object

Object类的包路径:java.lang.Object

Object对象的常用方法:

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
32
33
34
35
package com.itheima.常见类和对象;

public class ObjectClass {
public static void main(String[] args) {
Object obj = new Person();
//object对象的常用方法
// TODO toString(),将对象转换成字符串
String s = obj.toString();
System.out.println(s);

// TODO 获取对象的内存地址
int i = obj.hashCode();
System.out.println(i);

// TODO 判断两个对象是否相等,如果相等返回true,不相等返回flase
// equals方法比较对象时,默认比较的是内存地址
Person person = new Person();
boolean equals1 = obj.equals(person);
boolean equals2 = obj.equals(obj);
System.out.println(equals1);//false
System.out.println(equals2);//true

//TODO getClass获取对象的类型信息
Class<?> currentClassName = obj.getClass();
System.out.println(currentClassName.getSimpleName());
}

}
class Person{

}

class user extends Person{

}

常见类和对象——数组

语法

  • 声明一个数组:数据类型[] 数组名;

  • 初始化一个数组:

    • 静态初始化:静态初始化:数组变量的声明和数组元素的赋值同时进行

      1
      2
      3
      4
      //声明一个数组 : 数据类型[] 数组名;
      int[] nums;
      //也可以下面这种方式,也表示数组的长度为5 - 静态初始化:数组变量的声明和数组元素的赋值同时进行
      nums = new int[]{1,2,3,4,5};
    • 动态初始化:动态初始化:数组变量的声明和数组元素的赋值分开进行

      1
      2
      3
      int[] num1;
      //初始化一个数组:表示创建一个长度为5的数组 - 动态初始化:数组变量的声明和数组元素的赋值分开进行
      num1 = new int[5];

数组的访问:通过数组名[下标(索引)]来访问

数组的修改:数组名[索引] = 新值

数组的特点

数组的特点:(学习时注意与JS中的数组进行区分。)

  1. 数组中的元素在内存中是依次紧密排列的,有序的。
  2. 数组是引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型。
  3. 数组一旦初始化完成,其长度就确定了,并且长度不可更改(重点)
  4. 创建数组对象会在内存中开辟一块”连续的空间”。占据空间的大小,取决于数组的长度和数组中元素的类型。

数组的默认值

在 Java 中,当你创建一个数组但没有给每个元素赋初值时,Java 会自动为数组的每个元素分配一个默认值。这个默认值取决于数组元素的数据类型。下面是各种基本数据类型数组的默认值:

  1. 整型 (int[], short[], byte[]): 默认值为 0.
  2. 长整型 (long[]): 默认值为 0L.
  3. 浮点型 (float[]): 默认值为 0.0f.
  4. 双精度浮点型 (double[]): 默认值为 0.0d.
  5. 字符型 (char[]): 默认值为 \u0000 (即 ASCII 的 NUL 字符).或0
  6. 布尔型 (boolean[]): 默认值为 false.

例如:

1
2
3
4
5
6
int[] intArray = new int[10]; // 每个元素都是 0
long[] longArray = new long[5]; // 每个元素都是 0L
float[] floatArray = new float[3]; // 每个元素都是 0.0f
double[] doubleArray = new double[7]; // 每个元素都是 0.0d
char[] charArray = new char[4]; // 每个元素都是 '\u0000'
boolean[] booleanArray = new boolean[2]; // 每个元素都是 false

引用数据类型的数组,默认值是 null。例如:

1
2
String[] stringArray = new String[5]; // 每个元素都是 null
Object[] objectArray = new Object[10]; // 每个元素都是 null

请注意,这些默认值只适用于使用 new 关键字创建的数组。如果你声明一个数组但没有用 new 显式地分配内存,则不能访问这些默认值,因为这样的数组实际上并未创建。

一维数组内存解析

image-20240830172622604

该图传达的意思有:

  • 数组在内存中时,栈中存放的是数组的首元素的地址。
  • new出来的内容是在堆中的,通过赋值操作把响应的地址赋值给变量。
  • 通过new关键字出来的会新开辟一块内存空间(重要)

第二张图:

image-20240830173324148

这张图传达的意思:

  • 如果我们直接进行赋值,那么就是将数组的内存地址赋值给新的变量,进而两个变量指向同一个数组。
  • 在该图中我们没有使用new关键字进行定义新变量,所以没有开辟新的内存空间

常见类和对象——二维数组

二维数组的定义

二维数组可以理解为一个矩阵。

1
2
3
4
5
6
7
8
9
//二维数组(可以简单理解为3X3的表格)
String[][] names = new String[3][3];
//二维数组的遍历
for(int row= 0 ;row<3;i++){
for(int col =0; col < 3; j++){
System.out.print(names[row][col]);
}
System.out.println(" ");
}

数组——应用

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itheima.常见类和对象;

public class MaoPaoPaiXu {
// TODO 将一个数组的最大的数放在数组最后面
public static void main(String[] args) {
int[] nums = {1,4,3,52};
//注意不能索引到最后一个数,因为最后一个数的下一位没有数据
//左边和右边比较,我们先找出最大的数,让他放在数组最后一个元素位置
for(int i = 0;i< nums.length-1;i++){
int num1 =nums[i];
int num2 =nums[i+1];
if(num1>num2){
nums[i] = num2;
nums[i+1] = num1;
}
}
for (int num : nums) {
//打印结果为1,3,2,4,5
System.out.println(num);
}
}

}

上面这个只是找出了我们的最大的数放到了数组最后面,那么我们的冒泡排序的结果应该1,2,3,4,5所以稍加改动如下:

解析:那么在上面的基础上,我们每次把最大数放在后面不就可以了吗?也就是说第一次比较5个数,那么5最大放在后面,第二次比较4个数一定是4最大,我们把四放在四个数的最后面。依次类推,不就完成了吗?

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
//代码逻辑(冒泡排序)
package com.itheima.常见类和对象;

public class ErWeiShuZu {
// TODO 将一个数组的最大的数放在数组最后面
public static void main(String[] args) {
int[] nums = {1,4,3,52};
//注意不能索引到最后一个数,因为最后一个数的下一位没有数据
//左边和右边比较,我们先找出最大的数,让他放在数组最后一个元素位置
for (int i = 0; i < nums.length; i++) {
//当i=0时,我们下面的循环就找5个数的大小,等于1就找4个数的大小.....
for (int j = 0; j < nums.length - i - 1; j++) {
int num1 = nums[j];
int num2 = nums[j + 1];
if (num1 > num2) {
nums[j] = num2;
nums[j + 1] = num1;
}
}
}


for (int num : nums) {
System.out.println(num);
}
}
}

思路图解冒泡排序

image-20240622152436992

选择排序

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
		//选择排序
int[] nums = {1,4,3,52};
//第一步:找到最大值的索引
for (int j = 0; j < nums.length; j++) {
//将最大值索引定为0,因为0已经被用了所以下面的循环从1开始
int maxIndex = 0;
for (int i = 1; i < nums.length-j; i++) {
int num1 = nums[i];//4 3 2 5
int num2 =nums[maxIndex];//1 4 4 4
if (num1>num2){
//循环完成后我们的最大值索引就被找到了
maxIndex = i;
}

}
//把最后一个数和我们的最大值索引对应的数进行交换就完成了将最大的数放在数组最后面
int num3 = nums[nums.length-j-1];
int num4 = nums[maxIndex];
nums[nums.length-j-1] = num4;
nums[maxIndex] = num3;
}
for (int num : nums) {
//打印的结果为1,2,3,4,5
System.out.println(num);
}

二分查找法

  • 前提:数据有序。

解释:二分查找法,又称折半查找(Binary Search),是一种在有序数组中高效查找特定元素的算法。其核心思想是通过将待查找区间反复减半来逼近目标值,直至找到目标或者确定目标不存在于数组中。以下是二分查找的基本步骤:

  • 初始化: 确定查找区间的初始边界,通常是整个数组的开始索引(设为left)和结束索引(设为right),或者说是数组的第一个元素和最后一个元素的索引。

  • 计算中间索引: 在每一轮查找中,计算当前查找区间中间元素的索引 mid = (left + right) / 2(实际应用中为防止整数溢出,通常写作 mid = left + (right - left) / 2)。

比较与分支:

  • 如果数组中间元素array[mid]正好等于目标值,查找成功,返回中间元素的索引mid
  • 如果array[mid]小于目标值,则说明目标值位于中间元素右侧的子区间,因此更新查找区间为left = mid + 1
  • 如果array[mid]大于目标值,则目标值位于中间元素左侧的子区间,更新查找区间为right = mid - 1
  • 重复步骤2和3,直到left > right,此时说明目标值不在数组中,查找失败。

二分查找的时间复杂度为O(log n),其中n是数组中的元素数量。这是因为每次操作都将查找范围减半,因此大大减少了需要检查的元素数量,尤其是在处理大数据集时,这种算法的效率优势更为明显。但需要注意的是,二分查找的前提是数组必须是有序的,且通常适用于静态数据结构,因为对数组的插入和删除操作可能会破坏其有序性,从而增加维护成本。

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
package com.itheima.常见类和对象;
//演示的是二分法查找,前提是必须数组有序
public class SelectPaiXu {
public static void main(String[] args) {
int nums[]={1,2,3,4,5,6,7};
//定义查找的目标数
int targetNumber = 5;
//定义开始的索引
int startIndex = 0;
//定义中间值索引
int middle = 0;
//定义结束索引
int endIndex = nums.length - 1;
//查找中间值索引
while (startIndex <=endIndex ){
middle= (startIndex+endIndex) / 2;
if(nums[middle] > targetNumber){
endIndex = middle -1;
}else if(nums[middle]<targetNumber){
startIndex= middle+1;
}else {
break;
}
}
//打印中间值索引
System.out.println("数据的位置是:"+middle);
}
}

常见类和对象——包装类

在Java中,基本数据类型如 int, char, boolean 等都有对应的引用类型,也称为包装类。这些包装类提供了对基本数据类型的一些额外功能,比如自动装箱/拆箱、比较、序列化等。以下是Java中所有基本数据类型的包装类:

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

包装类的特点

  1. 实例化:包装类是对象,因此需要通过 new 关键字实例化。
  2. 不可变性:所有的包装类都是不可变的(immutable),这意味着一旦创建了包装类的对象,就不能更改其值。
  3. 方法:包装类提供了许多有用的方法,例如转换、比较、解析等。
  4. 装箱与拆箱:从Java 5开始,Java支持自动装箱(将基本类型自动转换为包装类)和自动拆箱(将包装类自动转换为基本类型)。

示例

下面是一些使用包装类的例子:

创建包装类对象

1
2
3
4
5
6
7
8
9
10
Integer myInt = new Integer(10);  // 明确地创建对象
Integer myAutoBoxedInt = 20; // 自动装箱

// 拆箱
int primitiveInt = myInt; // 自动拆箱

// 创建其他包装类对象
Double myDouble = new Double(10.5);
Character myChar = new Character('A');
Boolean myBool = new Boolean(true);

使用包装类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 解析字符串为整数
Integer parsedInt = Integer.parseInt("123");

// 获取最大值
int maxValue = Integer.MAX_VALUE;

// 比较两个整数
boolean isEqual = Integer.compare(10, 10) == 0;

// 将整数转换为字符串
String intAsString = Integer.toString(10);

// 获取一个整数的二进制表示
String binaryString = Integer.toBinaryString(10);

使用包装类的静态方法

1
2
3
4
5
6
7
8
9
// 比较两个整数
int compareResult = Integer.compare(10, 20);

// 检查一个整数是否为偶数
boolean isEven = (10 & 1) == 0;

// 最大值和最小值
int max = Math.max(10, 20);
int min = Math.min(10, 20);

特殊情况

对于 Integer 类,Java 为了节省内存,对于 -128127 之间的整数,会缓存这些值的对象。这意味着在这个范围内使用自动装箱时,实际上使用的是同一个对象。

1
2
3
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // 输出 true,因为a和b指向同一个对象

但是,超出这个范围时,对象不再是同一个:

1
2
3
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // 输出 false,因为a和b是不同的对象

将基本数据类型转换为包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.itheima.常见类和对象;

public class BaoZhuangLei {
public static void main(String[] args) {
int i =1;
//将基本数据类型转换为包装类型
Integer i1 = Integer.valueOf(i);
//也可以写成,这个过程称为自动装箱
Integer i2 =i;

//自动拆箱
int j = i1.intValue();
//也可以写成
int j1 = i1;
}
}

5)常见类和对象——日期和日历

日期类:

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
package com.itheima.常见类和对象;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTest {
public static void main(String[] args) throws ParseException {
//Date :日期类
//Calendar :日历类
Date d = new Date();
System.out.println(d);
// Java格式化日期格式:
//y(Y)->年-> yyyy
//m(M)->MM:月份,mm:分钟
//d(D)->dd :一个月中的日期,D:一年中的日期
//h(H)->h:12进制,HH:24进制
//s(S)->s :秒,S:亳秘

//Date转换为String
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateFormatString =sdf.format(d);
System.out.println(dateFormatString);

//string转换为Date
String dateString="2022-06-01";
Date parseDate = sdf.parse(dateString);
System.out.println(parseDate);
}
}

日历类:

1
2
3
4
5
6
7
8
9
//拿到日历的对象
Calendar instance =Calendar.getInstance();
//打印日历中的各种属性
System.out.println(instance.get(Calendar.YEAR)); System.out.println(instance.get(Calendar.MONTH));
System.out.println(instance.get(Calendar.DATE));
//在日历中找到当前时间
instance.setTime(new Date());

instance.add(Calendar.YEAR,1);

6)常见类和对象——工具类

  • 工具类不应该创建对象才能使用,也就意味着,可以直接使用类中的属性和方法,一般都声明为静态的。
  • 工具类对外提供的属性和方法都应该是公共的。
  • 为了使开发者方便,应该尽量提供丰富的方法和属性。

第四章 异常

Java中的异常分为两大类

  • 可以通过代码恢复正常逻辑执行的异常,称之为运行期异常:RuntimeException

  • 不可以通过代码恢复正常执行逻辑的异常,称之为编译异常:

    Exception

异常处理的语法:

try:尝试(放的是可能会出现问题的代码)

catch:捕捉,捕捉多个异常时,优先选择小的异常,然后将其抛出

finally:最终

1
2
3
4
5
6
7
8
9
10
try{
//可能会出现异常的代码
//如果出现异常那么JVM会将异常进行封装,形成一个具体的异常类然后将这个异常抛出
}catch(抛出的异常对象 对象引用){
//异常的解决方案
}catch(){

}finally(){
//最 终要执行的逻辑(不论异常处没处理)
}

异常的常见类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// TODO 异常
//1.除数为0的算术异常:java.lang.ArithmeticException
//运行期异常
int i = 0;
if(i!=0){
int j= 10 /i;
}
//2:空指针异常:java.lang.NullPointerException,调用了一个成员对象或成员方法为空(null)对象的属性或方法时,就会发生异常
User3 user = null;
System.out.println(user.tostring());

//3:索引越界,针对数组,字符串索引越界,针对字符串


class User3 {

}

如果方法中可能会出现问题,那么需要提前声明,告诉其他人,我的方法可能会出现问题。这时,我们需要使用throws关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String args[]) throws ArithmeticException{
User user = new User();
int i =10;
int j =0;
user.test(i,j);
}


class User8
public void test(int i,int j) throws ArithmeticException {
System.out.println(i/j);

}

那么在运行期的异常,他不会抱任何问题,因为只要你的逻辑对了,代码就能继续运行,那么我们该如何让他出现提示,需要解决异常呢?下面这种方法,我们手动抛出异常对象,但是使用的是throw关键字,然后new出异常对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String args[]) throws Exception{
User user = new User();
int i =10;
int j =0;
user.test(i,j);
}

class User8{
public void test(int i,int j) throws Exception {
try{
System.out.println(i/j);
}catch(ArithmeticException e)
//这里演示手动抛出异常
throw new Exception();
}
}

(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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.itheima.Exception;

public class ZiDingYiException {
public static void main(String[] args) throws Exception {
String username = "admin";
String password ="admin";
//对异常进行处理
try {
//可能出现异常的代码
loginSystem(username,password);
}catch (AccountException e){
System.out.println("账号输入有误,请重新输入");
}catch (PasswordException e){
System.out.println("密码输入有误,请重新输入");
}
} //如果下面是运行期异常(RuntimeException)这里可以不用抛出异常
//如果下面是编译期异常(Exception),则需抛出异常
public static void loginSystem(String username,String password) throws AccountException,PasswordException{
if(!"admin".equals(username)){
//创建异常对象
throw new AccountException("用户名不正确");

}
if(!"admin".equals(password)){
throw new PasswordException("密码不正确");
}
System.out.println("登录成功");
}
}
//账户异常
class AccountException extends loginException{

public AccountException(String message) {
// 调用父类里面的方法,传入message参数,详细可以在代码中ctrl+左键点击查看
super(message);
}
}
//密码异常
class PasswordException extends loginException{
public PasswordException(String message){
super(message);
}
}
//自定义异常,如果这里继承运行期异常(RuntimeException),那么上面就不需要抛出,如果是编译期异常(Exception),责需要抛出异常
class loginException extends RuntimeException{
public loginException(String message){
super(message);
}
}

ArrayList详解

1.怎么学?

  • arrayList会提供创建容器对象的方式

  • 会提供相应方法对容器进行操作

    增 删 改 查

创建对象 ====> 添加增删改查方法 ====> 了解集合特点

2.ArrayList常用方法

构造器

ArrayList
是用的最多、最常见的一种集合。

1
2
3
4
5
6
7
//创建一个空的集合对象
public ArrayList()
//存储任意数据类型
ArrayList list = new ArrayList();
/** 也可以这么写 */
Array<Object> list = new ArrayList();

常用方法名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//将指定的元素添加到此集合的末尾
public boolean add(E e)
//在此集合中的指定位置插入指定的元素
public void add(int index,E element)
//返回指定索引处的元素
public E get(int index)
//返回集合中的元素的个数
public int size()
//删除指定索引处的元素,返回被删除的元素
public E remove(int index)
//删除指定的元素,返回删除是否成功
public boolean remove(Object o)
//修改指定索引处的元素,返回被修改的元素
public E set(int index,E element)

3.从集合中遍历元素,并筛选出元素删除它,应该如何操作才能不出bug?
方式一:每次删除一个数据后,索引-1。

方式二:从集合后面遍历然后删除,可以避免漏掉元素。

集合中常用的接口

1.Collection接口(面对单一的数据处理)

常用子接口:List,Set,Queue

List:按照 插入顺序保存数据,数据可以重复

具体实现类:ArrayList,LinkedList

Set:集,无序保存,数据不能重复

具体实现类:HashSet(面对K-V键值对的数据处理)

Queue:队列

具体实现类:ArrayBlockingQueue

1.Map接口

具体实现类:hashMap,HashTable

什么是ArrayList?

  • 动态数组:底层由数组实现,但可自动扩容(默认初始容量为10)

  • 泛型支持:只能存储对象(如 Integer, String 等),需用包装类代替基本类型

  • 属于 java.util:使用前需要导入:

    • import java.util.ArrayList;
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      ## ArrayList的操作

      - **创建ArrayList**

      ```java
      // 创建一个存储 String 的 ArrayList
      ArrayList<String> names = new ArrayList<>(); // 空列表,初始容量10

      // 创建指定初始容量的 ArrayList
      ArrayList<Integer> numbers = new ArrayList<>(20); // 初始容量20

      // 通过已有集合快速初始化
      ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana"));

ArrayList常用方法

  • 增:以上面的names为例

    1
    2
    3
    names.add('xiaoli')
    //按索引位置增加
    names.add(1,'xiaohua')
  • 1
    2
    3
    4
    5
    6
    //按索引删除,返回被删除的元素
    names.remove(1)
    //删除第一个出现的内容,返回是否删除成功
    names.remove('xiaoli')
    //删除所有元素
    names.clear()
  • 1
    2
    //参数两个:索引,替换的值
    names.set(1, "C++"); // 将索引1的元素替换为"C++"
  • 1
    2
    String first = names.get(0);   // 获取索引0的元素(若越界会抛 IndexOutOfBoundsException)调用.get()方法 0 表示第一个元素
    int size = list.size(); // 获取当前元素数量(不是容量!)
  • 判断存在性

    1
    2
    boolean isEmpty = list.isEmpty();      // 是否为空
    boolean exists = list.contains("Go"); // 是否包含"Go"

遍历 ArrayList

方法有三:普通for循环

1
2
3
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

增强for循环

1
2
3
for (String item : list) {
System.out.println(item);
}

迭代器

1
2
3
4
5
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}

⚠️ 注意事项

1. 自动装箱与性能

  • 存储基本类型需用包装类(如 ArrayList<Integer>),频繁操作可能影响性能

  • 示例:

    1
    2
    3
    ArrayList<Integer> nums = new ArrayList<>();
    nums.add(5); // 自动装箱:int → Integer
    int num = nums.get(0); // 自动拆箱:Integer → int

2. 线程不安全

  • ArrayList 非线程安全,多线程环境下需手动同步或改用 Vector / CopyOnWriteArrayList

3. 扩容代价

  • 添加元素时若容量不足,会创建新数组并复制元素(时间复杂度 O(n))
  • 优化:预分配足够容量(如 new ArrayList<>(1000)

💡 进阶技巧

1. 转换为数组

1
String[] arr = list.toArray(new String[0]); // 转换为 String[]

2. 排序

1
Collections.sort(list); // 自然顺序排序(如字符串按字母顺序)

3. 过滤元素(Java 8+)

1
list.removeIf(item -> item.length() > 5); // 删除长度超过5的字符串

LinkedList详解

LinkedList = Linked(链接) + List

泛型

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
package com.itheima.List;

import java.util.ArrayList;

public class ListQuestion {
//todo 集合
public static void main(String[] args) {
//todo 泛型语法<>中放你要在集合中存储的数据类型
User user = new User();
//如果没有尖括号里面的内容,表示我在这个容器中可以存储任何类型的数据
MyContainer<User> myContainer = new MyContainer();
//会报错,因为我已经用尖括号限定了容器类类型
// MyContainer.data = new Object();
}
}


// todo 容器类
class MyContainer<C>{
public C data;
}

class User{

}

比较器

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
32
package com.itheima.List;

import java.util.ArrayList;
import java.util.Comparator;

public class ListSort {
public static void main(String[] args) {
ArrayList list = new ArrayList();

list.add(1);
list.add(2);
list.add(3);
//new一个比较类的对象
NumberComparator comparator = new NumberComparator();
//todo 集合中的数据进行比较
list.sort(comparator);
System.out.println(list);
}
}
//创建一个比较类,实现Comparator接口
class NumberComparator implements Comparator<Integer>{

@Override
public int compare(Integer o1, Integer o2) {
//todo 如果第一个数大于第二个数,返回结果为正数,表示升序排序
// return o1 - o2;
//todo 如果第一个数小于第二个数,返回结果为负数,表示升序降序
return o2 - o1;
//todo 如果第一个数等于第二个数,返回结果为0。
// return 0;
}
}

HashSet

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
package com.itheima.List;

import java.util.HashSet;

public class HashSetTest {
public static void main(String[] args) {
//HashSet:Hash + Set
//Hash:哈希算法,散列(无序)
//ArrayList:数组
//LinkedList:
HashSet hashSet = new HashSet();

//todo 增加数据
hashSet.add("lisi");
hashSet.add("zhangsan");
hashSet.add("wangwu");
// todo 修改数据(先删除,在增加)
hashSet.remove("zhangsan");
hashSet.add("xiaoli");
//todo 遍历数据
for (Object obj : hashSet){
System.out.println(obj);
}
// todo 转换成数组
Object[] objects = hashSet.toArray();
System.out.println(hashSet);
}
}

HashSet处理重复数据

重写equals和hashcode方法

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.itheima.List;


import java.util.HashSet;

public class HashSetTestForNum {
public static void main(String[] args) {
//HashSet的底层是:数组 + 链表 结构
HashSet hashSet = new HashSet();
User1 user1 = new User1();
User1 user2 = new User1();
User1 user3 = new User1();
user1.id=1001;
user1.name="zhangsan";
user2.id=1001;
user2.name="zhangsan";
user3.id=1002;
user3.name="lisi";

hashSet.add(user1);
hashSet.add(user2);
hashSet.add(user3);
System.out.println(hashSet);
}
}
class User1{
public String name;
public int id;
//重写hashcode方法和equals
//类似于内存地址
@Override
public int hashCode() {
return id;
}

@Override
public boolean equals(Object obj) {
if(obj instanceof User1){
User1 testObj = (User1) obj;
if(testObj.id == this.id){
if(testObj.name.equals(this.name)){
return true;
}
}
return false;
}else{
return false;
}
}


@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name=" + name +
'}';
}
}

Queue接口

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
32
33
34
35
36
37
38
39
40
package com.itheima.Queue;

import java.util.concurrent.ArrayBlockingQueue;

public class TestQueue {
public static void main(String[] args) throws Exception{
//todo 了解我们的Queue
// ArrayBlockingQueue = Array(数组) + Blocking(阻塞,等待)
// 所以queue底层还是数组
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
//添加数据
queue.add("zhangsan");
queue.add("lisi");
queue.add("wangwu");
//由于我长度只给了3,所以只能存三个数据,下面这个数据无法存储,报错 Queue full,代表queue存满了
// queue.add("xiaoli");
//添加数据(put)方法,该方法能够体现出阻塞这一概念,当我添加的数据大于长度时,程序会继续运行,不会停止
// queue.put("zhangsan");
// queue.put("lisi");
// queue.put("wangwu");
//添加数据offer方法,该方法会返回一个布尔类型的值,添加成功返回true,添加失败(超出queue存储长度)返回false
// boolean zhangsan = queue.offer("zhangsan");
// boolean lisi = queue.offer("lisi");
// boolean wangwu = queue.offer("wangwu");
// System.out.println(zhangsan);
// System.out.println(lisi);
// System.out.println(wangwu);
//拿取数据(poll方法)
// System.out.println(queue.poll());
// System.out.println(queue.poll());
// System.out.println(queue.poll());

//拿取数据(take方法),当我去一个不存在的数据时,不会报错,程序不会结束,体现了一个等待,阻塞的过程
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());

System.out.println(queue);
}
}

HashMap(处理键值对数据(K,V))

数据存储无序

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.itheima.HashMap;
import java.util.*;
public class TestHashMap {
public static void main(String[] args) {
//HashMap = Hash + Map
//todo 处理键值对{K=V}
//创建HashMap对象
HashMap<String,String> map = new HashMap();


//添加数据put方法,当put传入的键相同时,相当于修改操作,返回被修改的值
map.put("zhangsan","1");
System.out.println(map.put("zhangsan", "2"));//返回1,并且zhangsan键对应的value值被改为了2
map.put("lisi","2");
map.put("wangwu","3");
System.out.println(map);
//todo 查询数据(返回被删除的键对应的value值)
System.out.println(map.get("zhangsan"));
//todo 删除数据
map.remove("zhangsan");


//todo 添加数据putIfAbsent方法,这个方法会检查该map里面有没有这个键值,如果有,则不做任何操作,如果没有则插入数据
map.putIfAbsent("Xiaoli","4");
map.putIfAbsent("lisi","2");
//todo 修改数据replace方法
map.replace("Xiaoli","8");
//todo 清空
// map.clear();
//todo 获取map集合中所有的key
Set setKey = map.keySet();
// for(Object i : setKey){
// System.out.println(map.get(i));
// }
//todo 获取所有的值
Collection value = map.values();
System.out.println(value);
//判断集合中包含某个指定的value值
System.out.println(map.containsValue("1"));
System.out.println(map);
//todo 获取键值对对象
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+"="+ entry.getValue());
}

}
}

HashTable

1
2
3
4
5
6
7
8
9
10
11
package com.itheima.HashTable;

public class TestHashTable {
public static void main(String[] args) {
//todo HashTable
// HashTable底层容量默认11
// HashTable的K,V不可以为null值
// HashTable使用的是HashCode
//基本操作和HashMap一致(第(8)小节内容)
}
}

迭代器

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
package com.itheima.HashTable;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class DieDaiQi {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String,Integer>();
map.put("a",1);
map.put("b",2);
map.put("c",3);
//todo 获取map集合当中的key值,返回key值的集合
Set<String> setKeys = map.keySet();

//TODO 使用迭代器对集合map进行处理
//todo 创建迭代器对象(iterator)
Iterator<String> iterator = setKeys.iterator();
//hasNext用于判断是否存在下一条数据
while (iterator.hasNext()){
//获取下一条数据
String key = iterator.next();
if("b".equals(key)){
iterator.remove();
}
System.out.println(map);
}
System.out.println(map);
}
}

1、如何拿到用户的输入

有时候的一些业务是需要用户和系统互动的,这时,我们就需要来接收用户的互动,在java中,通常这么做

1
2
3
4
//新建一个扫描器对象sc
Scanner sc = new Scanner(System.in);
//定义一个变量来接收用户输入,类型要看你接受的是字符串还是数据而变动
数据类型 变量 = sc.nextInt();

举个例子,比如我下面这段代码

1
2
3
4
Scanner sc = new Scanner(System.in);
System.out.println("请您输出查询的id");
int id = sc.nextInt();
operator.searchMovieById(id);

很好理解,接收用户输入的数字id然后把他传给operator.searchMovieById(id);方法完成代码逻辑。

字符串类型

  • 方式一:
1
2
3
String name = "小黑"

String schoolName = "黑马程序员"
  • 方式二:调用String类的构造器初始化字符串对象
1
2
3
4
5
6
7
public String() //创建一个空白字符串对象,不含有任何内容

public String(String original) // 根据传入的字符串内容,来创建字符串对象

public String(char[] chars)// 根据字符数组的内容,来创建字符串对象

public String(byte[] bytes) //根据字节数组的内容,来创建字符串对象

(2)有关字符串的一些方法

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
//查询字符串长度(字符个数)
public int length()
//获取某个索引位置处的字符返回
public char charAt(int index)
//将当前字符串转换成字符数组返回
public char[] toCharArray():
//判断当前字符串与另一个字符串的内容一样,一样返回true
public boolean equals(object anobject)
//判断当前字符串与另一个字符串的内容是否一样(忽略大小写)
public boolean equalsIgnoreCase(String anotherString)
//根据开始和结束索引进行截取,得到新的字符串(包前不包后)
public String substring(int beginIndex, int endIndex)
//从传入的索引处截取,截取到末尾,得到新的字符串返回
public String substring(int beginIndex)
//使用新值,将字符串中的旧值替换,得到新的字符串
public String replace(CharSequence target,CharSequence replacement)
////判断字符串中是否包含了某个字符串
public boolean contains(Charsequence s)
//判断字符串是否以某个字符串内容开头,开头返回true,反之返回false
public boolean startsWith(String prefix)
//判断字符串是否以某个字符串内容结尾,结尾返回true,反之返回false
public boolean endWith(String prefix)
//把字符串按照某个字符串内容分割,并返回字符串数组回来
public String[] split(String regex)
//indexOf用于获取数据在字符串中第一次出现的位置
String s = "world hello hello world hello"
s.indexOf("world");
//lastIndexOf用于获取数据在字符串中最后一次出现的位置
s.lastIndexOf("hello");

(3)字符串进行比较有哪些需要注意的?

1、字符串的比较使用==比较好吗?为什么?什么时候使用==?

  • 不好,对于字符串对象的比较,==比较的是地址,容易出业务bug
  • 基本数据类型的变量或者值应该使用==比较。

2、开发中比较字符串推荐使用什么方式比较?
使用String提供的equals方法,它只关心字符串内容一样就返回true,

1
2
3
方法名
public boolean equals(object anobject)
public boolean equalsIgnoreCase(String anotherstring)

(4)随机产生验证码(使用字符串方式)

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
package com.itheima.StringController;

import java.util.Random;

//随机产生验证码
public class StringDemo2 {
public static void main(String[] args) {
System.out.println(randomCode(4));
System.out.println(randomCode(5));
System.out.println(randomCode(6));
}

public static String randomCode(int tempCode){
Random r = new Random();
String code = "";
String data ="abcdefghijklmnopqrstuVWXyZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
//我们需要5位验证码调用for循环
for (int i = 0; i < tempCode; i++) {
int j = r.nextInt(data.length());
//将字符交给code
code += data.charAt(j);
}
return code;
}
}

(5)两种方式的区别

  • 双引号给出的字符串对象,存在于堆内存中的常量池中,相同内容只会存储一份。
  • new 字符串对象,每new一次都会在堆内存中产生一个字符串对象。

字符串大小写转换

1
2
3
4
5
String s ="Hello,World"
//字符串变小写
s.toLowerCase()
//字符串变大写
s.toUpperCase()

StringBuilder方法

java提供的一种对字符串进行拼接,反转的快捷类

使用实例:

1
2
3
4
5
6
7
8
9
10
11
StringBuilder s = new StringBuilder();
//拼接字符串
for (int i = 0; i < 10; i++) {
s.append(i);
}
System.out.println(s);
//反转字符串
s.reverse();
//在字符串指定位置插入
s.insert(1,"abc");
System.out.println(s.toString());

Java中遇到的各种问题

(1)Mybatis与java

​ 在Mybatis和java联合编写xml文件时,有关Mapper.xml(编写SQL的)文件中,增删改查标签中,使用parameterType属性指定传递参数类,resultType属性指定返回结果类型,resultMap标签完成当数据库表字段和Java实体类属性名或类型不一致时的手动映射,另外,SQL文中,where条件中只有一个参数时,推荐使用基本类型(int,short,char,byte,float,double,long,boolean)+String,当有多个参数时,使用Java实体类,接下来,我们来讲讲具体的用法。
来看示例:

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
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--上面是文件头,下面是这个xml文件对应的mapper文件的路径-->
<mapper namespace="com.ruoyi.publicController.mapper.proDetailMapper">
<resultMap id="proDetailsResult" type="proSelectDetails">
<result property="proName" column="pro_name"/>
<result property="profounder" column="user_name"/>
<result property="proTrack" column="pro_track"/>
<result property="proScore" column="pro_score"/>
</resultMap>

<resultMap id="countResult" type="proSelectDetails">
<result property="countOne" column="countOne"/>
<result property="countTwo" column="countTwo"/>
</resultMap>


<!--查询总表所有项目信息-->
<select id="selectAllInfo" resultMap="proDetailsResult" resultType="proSelectDetails">
<include refid="selectProDetailsVo"/>
</select>



<sql id="selectProDetailsVo">
SELECT DISTINCT pro_details3.pro_id, pro_name, pro_details3.pro_score, user_name, pro_track
FROM pro_details3
INNER JOIN judges_score ON judges_score.pro_id = pro_details3.pro_id
INNER JOIN pro_coreteam ON pro_details3.pro_id = pro_coreteam.pro_id
INNER JOIN sys_user ON sys_user.user_id = pro_coreteam.student_id
</sql>
<!--根据项目赛道查询项目信息-->
<select id="selectProDetailsByProTrack" resultMap="proDetailsResult" resultType="proSelectDetails">
<include refid="selectProDetailsVo"/>
where pro_track = #{proTrack}
</select>

1.parameterType
定义在增删改查标签中的属性,指定SQL文中where条件的参数类型,可以是基本数据类型(8个)或String或者对象类型
例如,根据id查询name,如果数据库中定义的字段id是int类型,就可以指定parameterType的属性值是int,可以参看下边的代码。

1
< select id="queryPersonNameById"  parameterType="int" resultType="String"></select>

2.resultType
也是定义在增删改查标签中的属性,用于指定查询结果或增删改执行结果的类型,同样可以是基本数据类型+String+对象类型,这里需要注意的是,当查询结果是多条时,无需写List,MyBatis底层会自动为我们生成List<数据类型>,我们只需指定类型即可
同样是上边的例子,根据id查询name,name在数据库中是varchar类型,resultType就指定成String类型,类型不区分大小写,尽量保持和Java一致,使用大写。

1
< select id="queryPersonNameById" parameterType="int" resultType="String"></select>

3.resultMap
当实体类的属性名和数据库表字段名或类型不一致时使用
例如,Person表中,定义的字段是id和name
Java实体类中,定义的是pid和pname,这种情况下,使用resultMap手动完成数据库和Java实体类属性的映射。
它是单独的< resultMap>标签,定义id(该属性是下面要用到的resultMap标签对应的值),type(该属性指定了结果应该映射到的Java类的完全限定名或别名。)通常使用在查询标签中
例如下边的代码
id标签完成数据库主键的映射
result标签完成数据库非主键字段的映射
property属性指定java实体类中的属性名,column是数据库字段名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxx.xxx.mapper.PersonMapper">
<resultMap type="person" id="personMapping">
<id property="pid" column="id"/>
<result property="pname" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
</resultMap>

<select id="queryAllPersonUseResultMap" resultMap="personMapping">
select * from person
</select>

</mapper>

(2)增强for循环

1
2
3
for (元素类型 参数 : 集合名称) {
// 使用element(参数)
}
  • 通常适配类型:它通常用于遍历数组或实现了Iterable接口的集合类(如ArrayListHashSet等)。

  • 增强for循环的优点:

    • 使用增强型for循环的优点

      • 简洁易读: 代码更简洁,不需要显式地处理索引。

      • 减少错误: 避免了手动操作索引可能带来的错误,例如越界访问。

    • 使用增强型for循环的限制

      • 只读访问: 增强型for循环适用于只读访问集合中的元素,如果需要修改元素或需要索引值,则需要使用传统的for循环。

      • 无法跳跃访问: 不能实现按特定步长跳跃访问元素,只能顺序遍历。