一、Java 的基本特性
-
跨平台性(Write Once, Run Anywhere)
通过 Java 虚拟机(JVM)实现,编译后的字节码能够在任何安装了 JVM 的操作系统上运行。
典型示例:在 Windows 平台开发的 Java 程序无需修改,即可在 Linux 或 macOS上进行运行。
而 C/C++ 等语言需要针对不同平台重新编译。 -
面向对象特性
封装:通过 private、protected 和 public 修饰符控制访问权限
继承:支持类的单继承以及接口的多继承。
*Java不能进行类的多继承 要实现多继承只能通过接口进行实现
多态:包括编译时的重载和运行时的重写。
*重载发生在同一个类中,需要方法名一致,其参数顺序,内容不同才可发生重载
*重写发生在继承体系中,其方法名与参数需与父类一致 -
自动内存管理(垃圾回收机制)
JVM 会自动回收不再使用的对象所占内存,
这一机制有效避免了 C/C++ 中常见的内存泄漏问题。 -
编码规范
类名采用大驼峰命名法,如:MyClass。
方法和变量采用小驼峰命名法,如:myMethod。
二、数据类型与变量
-
基本数据类型 与 默认值
-
在Java中基本数据类型(Primitive Data Types)共有8个,按照类型可以分为4大类:
-
整数类型
类型 说明 取值范围 默认值 byte 8位有符号整数 -2^7 --- 2^7-1
-128 --- 1270 short 16位有符号整数 -2^15 --- 2^15-1
-32,768 --- 32,7670 int 32位有符号整数 -2^31 --- 2^31-1
-2147483648 --- 21474836470 long 64位有符号整数 -2^63 --- 2^63-1
-9223372036854775808 --- 92233720368547758070L -
浮点类型
类型 说明 精度 默认值 示例 float 32位单精度浮点数 24 位二进制精度。
相当于 大约 6~7 位十进制有效数字
0.0f float pi = 3.14159f;
*float定义时需在末尾加上 f / F,否则编译器会视为double类型
double 64位双精度浮点数 53 位二进制精度
相当于 约 15~17 位十进制有效数字
0.0d double pi = 3.141592653589793d;
*后缀d可进行省略 -
字符类型
类型 说明 可表示内容 默认值 示例 char 16位Unicode字符
*与C语言不一致,C语言位ASCII码
可以表示从 '\u0000' 到 '\uffff' 的字符 '\u0000' char grade = 'J';
-
布尔类型
类型 说明 默认值 示例 boolean 只有true和false两个值
*需要注意,Java中没有0为假非0为真的说法
false char grade = 'J';
需要注意:在 Java 中,并不存在 “0 为假、非 0 为真” 的说法。也就是说,在使用 if、switch 等用于逻辑判断的语句时,只能基于 true/false 进行判断,不支持使用数字作为条件。
-
-
String(字符串)属于引用类型 不属于基本数据类型
-
变量的定义与作用域
-
变量,顾名思义,就是一个值可被改变的对象
-
作用域:
-
局部变量:当进入定义该变量的局部作用域时,变量被创建,并且只能在该作用域内使用;当退出该作用域时,变量随之销毁。
-
全局变量:从程序启动开始便存在,在整个程序运行期间始终有效,直到程序结束才被销毁
public class Test { public static int A = 10;// 全局变量 public static void main(String[] args) { int a = 10;//main方法的局部变量 for (int i = 0; i < 10; i++) { // i 为 for循环的局部变量 int j =0;// j是for循环块内的局部变量 } } }
-
-
-
引用类型
-
特点 说明 存储内容 对象的内存地址(引用) 作用 访问堆上的对象 默认值 null包含的类型 类、接口、数组、枚举 与基本类型的区别 基本类型存储值,引用类型存储引用(地址) -
内存分布图:

-
当引用类型被赋值时,实际上是将新对象的地址赋给该引用变量进行存储。
例如,下面的代码说明了这一点:
public class Test4 { public static void main(String[] args) { String s1 = "Hello"; //这里为简写 完整内容为:String s1 = new String("hello"); System.out.println(s1); s1 = "My Test"; System.out.println(s1); } }运行结果:
Hello
My Test -
其在内存中的过程如下图所示:

-
-
类型转换(自动 / 强制)
这正是自动类型转换(即隐式类型转换)的体现,该过程由编译器自动完成,无需显式操作
-
强制类型转换:
使用 (目标类型) 对象名 的形式,即可实现显式类型转换。 -
自动类型转换:
自动类型转换本质上与强制类型转换相同,都是对对象类型的转换。
区别在于:强制类型转换是显式进行的,由程序员手动指定;而自动类型转换是隐式进行的,由编译器自动完成。public class Test2 { public static void main(String[] args) { int intNumber = 42; double doubleNum = intNumber; // 隐式类型转换发生在这里 // double doubleNum = (double)intNumber; System.out.println("int 值: " + intNumber); System.out.println("转换后的 double 值: " + doubleNum); } }运行结果:
int 值: 42
转换后的 double 值: 42.0
-
-
final关键字作用(变量不可变)-
基本数据类型
-
当使用 final 修饰基本数据类型时,变量的值不可改变。
需要注意的是,变量必须先完成初始化,未初始化时可以通过赋值进行初始化;
一旦变量完成初始化后,该值便变为不可修改。public class Test3 { public static void main(String[] args) { final int a1 = 10; a1 = 10; // 编译报错,final变量不可修改 final int a2; a2 = 10; // 正确,a2尚未初始化,完成初始化赋值 a2 = 20; // 编译报错,a2已被初始化,不能再次修改 } }final 修饰变量,就像是一把锁,将该对象锁定在一个“箱子”中,
此时无法通过 变量名 = 新值 的方式对其进行修改。
-
-
引用类型
-
当使用 final 修饰引用类型时,引用变量中存储的对象地址不可更改。
需要注意的是,虽然地址不能变,但该地址指向的对象内容仍可修改,
除非该类型本身是不可变的,例如 String 类型。 -
参照下列代码:
class Dog { String name; int age; double weight; public Dog(String name, int age, double weight) { this.name = name; this.age = age; this.weight = weight; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", weight=" + weight + '}'; } } public class Test5 { public static void main(String[] args) { final Dog dog = new Dog("小黄",8,14.5); //dog = new Dog("小黑",4,7);//报错 dog被final所修饰,不能进行更改 System.out.println("修改前:" + dog.toString()); dog.age = 10; dog.name = "阿黄"; dog.weight = 20; System.out.println("修改后:" + dog.toString()); } }运行结果:
修改前:Dog{name='小黄', age=8, weight=14.5}
修改后:Dog{name='阿黄', age=10, weight=20.0}
-
-
由此可见,当使用 final 修饰引用类型时,确实是使引用变量本身不可变,即其存储的对象地址不能更改,但该地址所指向的对象内容仍然可以被修改。
三、运算符与表达式
-
算术、赋值、关系、逻辑运算符
-
算数运算符
-
基本四则运算符:
运算符 说明 注意事项 +加法运算符,用于两数求和 无 -减法运算符,用于求两数之差 无 *乘法运算符,用于求两数之积 无 /除法运算符,用于求两数之比 除数不能为 0 %求余运算符,用于求余数 可对小数取模,但不能对 0 取模 -
增量运算符
运算符 示例(以 a, b 为参数) 等价于(以 a, b 作为参数) 注意事项 +=a += ba = a + b无 -=a -= ba = a - b无 /=a /= ba = a / bb 不能为 0 *=a *= ba = a * b无 %=a %= ba = a % bb 不能为 0,且可为小数 - 自增/自减 运算符
运算符 说明 示例(以a为参数) 等价于(以a作为参数) ++ 前置++ ++a a = a +1 后置++ a++ a = a +1 -- 前置-- --a a = a - 1 后置-- a-- a = a -1 前置++(--)与后置++(--)区别 说明 区别 前置++ 先将变量进行 +1,然后再参与后续的运算操作(如求积等) 后置++ 先参与运算操作,之后再执行 +1 前置-- 先将变量进行 -1,然后再参与后续的运算操作 后置-- 先参与运算操作,之后再执行 -1
-
- 关系运算符
-
运算符 示例(以a,b为参数) 返回值(假设a =10,b = 20) 注意事项 == a == b false 不能连续 等于(a==b==c) != a != b true 不能连续 不等于(a != b != c) > a > b false 不能连续 大于 (a > b > c) >= a >= b false 不能连续 >= (a >= b >= c) < a < b true 不能连续 小于(a < b <c) <= a <= b true 不能连续 <= (a >= b >= c)
-
-
逻辑运算符
-
逻辑运算符主要包含:
||(或)、&&(与)、!(非),
其运算结果均为布尔类型(true或false)。 -
运算符 说明 表达式1 表达式2 返回值 && 全真为真 有假则为假 真 真 真 真 假 假 假 真 假 假 假 假 || 有一个为真则为真 真 真 真 真 假 真 假 真 真 假 假 假 ! 将结果取反 表达式 返回值 !真 假 !假 真 -
在
||和&&运算中,存在短路求值的情况。例如表达式:
10 < 20 || 10 / 0
虽然10 / 0会导致除以零错误,但程序依然输出true,这是因为短路求值生效了。所谓短路求值,理解起来非常简单:
-
对于
&&:如果左侧表达式为 false,右侧就不会再执行,直接返回 false。 -
对于
||:如果左侧表达式为 true,右侧就不会再执行,直接返回 true。
-
-
-
位运算符
-
位运算符操作的是二进制位。
常用的位运算符包括:
&(按位与)、|(按位或)、~(按位取反)、^(按位异或)。 -
按位与 &
-
全 1 输出 1 ,有 0 则输出 0
-
用途:一般用于清零操作,比如需要将 A(10110011)进行清零,那么将A与00000000进行按位与操作,则A就可以被清零
-
表达式 表达式A 表达式B 结果 A & B 1 1 1 0 0 0 1 0 0 0 1 0 示例: 表达式A 1011 1001 1111 表达式B 1001 0111 0011 结果 1001 0001 0011
-
-
按位或 |
-
有 1 输出 1,全 0 输出 0
-
用途:常用于设置某些位为 1,如 A 为 00000000,想将第三位设为 1,让 A 与 00000100 按位或,A 的第三位就会变为 1 。
-
表达式 表达式A 表达式B 结果 A | B 1 1 1 1 0 1 0 1 1 0 0 0 示例: 表达式A 1011 1001 1111 表达式B 1001 0111 0011 结果 1011 1111 1111
-
-
按位取反 ~
-
将对应位进行取反操作,即 0 变为 1,1 变为 0
-
表达式 表达式B 结果 ~ B 1 0 0 1 示例: 表达式B 1001 0111 0011 结果 0110 1000 1100
-
-
按位异或 ^
-
两位相同为0 ,不同为1
-
用途:用于识别唯一出现的数字。例如:A(1001)、B(1001)、C(0101),可以通过以下步骤进行异或运算:
- 先计算 A^B = 0000
- 再将结果与 C 异或:0000 ^ 0101 = 0101 最终结果 0101 即为唯一出现的数字 C。
-
表达式 表达式A 表达式B 结果 A ^ B 1 1 1 0 0 1 0 1 0 1 0 0 示例: 表达式A 1011 1001 1111 表达式B 1001 0111 0011 结果 0010 1110 1100
-
- 总结:
-
名称 符号 规则 应用层面 按位与 & 如果两个二进制位都为 1,则结果为 1,否则为 0 用于清零、掩码操作(保留特定位)、权限控制 按位或 | 如果两个二进制位中有一个为 1,则结果为 1,否则为 0 用于设置某些位为 1、合并标志位等 按位取反 ~ 如果该位为 0,结果为 1;为 1,结果为 0 用于取反、生成补码、快速计算 -x - 1按位异或 ^ 两位相同为 0,不同为 1 用于比较差异、加密解密、不使用临时变量交换两个数
-
-
位移运算
-
位移运算符共有三种,分别是:
<<(左移)、>>(右移)、>>>(无符号右移)。
它们的操作对象都是二进制位。 - 左移运算符(>>)
-
左移运算符会将二进制数的每一位向左移动指定的位数,高位溢出部分舍去,低位用 0 补齐。
-
格式
-
B << X -
B:二进制数
-
X:需要移动的位数
-
-
示例:
将1000 1100左移 1 位操作过程:
-
左移:
1000 1100 << 1 -
低位补 0 →
1 0001 1000 -
舍弃溢出位 →
0001 1000 -
最终结果 →
0001 1000
-
-
过程动画演示:

-
-
右移运算符(>>)
-
在 Java 中,
>>也被称为 算术右移(带符号右移)。 -
右移运算符会将二进制数的每一位向右移动指定的位数,高位空出的位置由符号位填充。
-
格式
-
B >> X -
B:二进制数
-
X:需要移动的位数
-
-
示例:
将1000 1100右移 3 位操作过程:
-
右移:
1000 1100 >> 3 -
高位补符号位(1) →
1111 0001 100 -
舍弃溢出位 →
1111 0001
-
-
最终结果 →
1111 0001 -
过程动画演示:

-
-
无符号右移 >>>
-
在 Java 中,
>>>也被称为 逻辑右移(无符号右移)。 -
无符号右移运算符会将二进制数的每一位向右移动指定的位数,高位空出的位置统一补 0(不考虑符号位)。
-
格式:
-
B >>> X-
B:二进制数
-
X:需要右移的位数
-
-
-
-
示例:
将1000 1100右移 3 位操作过程:
-
右移:
1000 1100 >> 3 -
高位补 0(无符号右移不补符号位) →
0001 0001 100 -
舍弃溢出位 → 0001 0001
-
-
最终结果 → 0001 0001
- 过程动画演示:

-
注意:Java中没有无符号左移!!
-
-
条件运算符
-
条件运算符只有一个:a ? b : c
-
表达式1 ? 表达式2 : 表达式3
-
当 表达式1 的值 为 true 的时候,整个表达式的值为 表达式2 的值
-
当 表达式1 的值 为 false 的时候,整个表达式的值为 表达式3 的值
-
-
该条件运算符也是Java中唯一一个三目运算符
-
注意事项:
-
表达式2 与 表达式3 需要是相同类型的数据,
除非它们之间能够发生自动类型转换(即隐式类型转换),
否则将会导致类型不匹配的编译错误。-
public static void main(String[] args) { int a = 10; int aa = 20; double b = 20.0; String s = "abc"; System.out.println(a > aa ? a : aa);//同类型 System.out.println(a > b ? a : b);//发生隐式转换 //System.out.println(a > s ? a : s);// a > s部分报错 类型不匹配 }运行结果:
20
20.0
-
-
表达式必须被使用,不能单独出现
-
public static void main(String[] args) { int a = 10; int b = 20; a > b ? a : b;//报错 该表达式必须被使用 }
-
-
-
-
运算符的优先级
-
| 类型 | 符号 | 说明 | 优先级(1~12) |
| 括号与成员访问 | () | 方法调用/强制类型转换 | 1 |
| [ ] | 数组访问 | ||
| . | 成员访问 | ||
| 单目运算符 | ++ | 后置++ | |
| -- | 后置-- | ||
| +(正号) | 表示一个数为正数 | 2 | |
| -(负号) | 表示一个数为负数 | ||
| ~(按位取反) | 将 1 变为0,0 变为 1 同属于位运算符 | ||
| !(逻辑非) | 将true 变 false ,false 变 true 同属于逻辑运算符 | ||
| ++ | 前置++ | ||
| -- | 前置-- | ||
| 算术运算符 | * | 乘法 | 3 |
| / | 除法 | ||
| % | 取模 | ||
| +(加法) | 求两数之和 | 4 | |
| -(减法) | 求两数之差 | ||
| 位移运算符 | << | 左移 | 5 |
| >> | 右移(带符号右移) | ||
| >>> | 无符号右移 | ||
| 关系与相等运算符 | < | 判断两数大小 | 6 |
| > | |||
| <= | |||
| >= | |||
| == | 判断两数是否相等 | 7 | |
| != | 判断两数是否不想等 | ||
| 位运算符 | & | 全 1 输出 1 ,有 0 输出 0 | 8 |
| ^ | 相同 输出 0,不同输出 1 | 9 | |
| | | 有 1 输出 1,全 0 输出0 | 10 | |
| 逻辑运算符 | && | 判断左右两边条件是否均成立 | 11 |
| || | 判断左右两边是否有条件成立 | 12 | |
| 三元运算符 | ? : | 简化版本的if else | 13 |
| 赋值运算符 | = | 进行赋值操作 | 14 |
| += | 将数值相加后赋值 | ||
| -= | 将数值相减后赋值 | ||
| *= | 将数值自乘后赋值 | ||
| /= | 将数值自除后赋值 | ||
| %= | 将数值自模后赋值 | ||
| <<= | 左移后赋值 | ||
| >>= | 右移后赋值(带符号右移) | ||
| >>>= | 无符号右移后赋值 | ||
| &= | 按位与后 赋值 | ||
| ^= | 按位取反后 赋值 | ||
| |= | 按位或后 赋值 | ||
| 分隔符 | , | 用于分隔 变量 方法参数等 | 15 |
-
' + ' 在字符串上的特别应用
-
使用 ' + ' 可以完成字符串拼接
-
public static void main(String[] args) { String a = "hello"; String b = " Java"; System.out.println(a + b); }运行结果:
hello Java
-
四、流程控制语句
-
if-else与switch的使用场景和陷阱(如:case穿透)-
if-else -
格式:
-
if (条件语句) { // 代码块 } else if (条件语句) { //代码块 } else { //代码块 }注意事项:
-
条件语句的结果必须是布尔类型
-
满足哪个条件,就会进入该条件对应的代码块,执行相应的代码
-
-
switch
-
格式:
switch (value) { case 1: // 代码块 case 2: // 代码块 case 3: // 代码块 default: // 代码块 } -
注意事项:
-
case后只能跟编译时常量,并且其类型必须与switch表达式的类型一致 -
当进入
switch后,会选择与case后的值相同的分支进入 -
switch中的表达式不能是浮点型(float、double)、long、boolean或自定义类型
-
case穿透:当从某个
case进入后,代码会一直向下执行,直到遇到break或执行完所有的case和default代码块
这种现象称为case穿透动图展示(展示有无
break时的区别):
下面是中途有 break 的情况下:
-
-
-
循环结构:
for、while、do-while-
for循环-
格式:
-
for (初始话语句;循环条件;迭代语句) { //循环体 } -
初始化语句:设置循环的起点,声明并初始化循环控制变量。此部分仅在循环开始时执行一次
-
循环条件:每次循环前检查的布尔表达式,若为
true执行循环体,若为false则终止循环 -
迭代语句:每次循环结束后更新循环控制变量,通常为递增/递减操作
-
-
for的变体:for each-
for each主要用于操作数组或集合。-
其格式如下:
-
for (元素类型 变量名 : 数组或集合) { // 循环体 } -
元素类型:必须与数组或集合中的元素类型一致
-
变量名:临时变量,表示当前迭代的元素
-
终止条件:当所有元素遍历完成后,循环结束
-
运行逻辑:将数组或集合中的元素一个一个赋值给
变量名,然后执行循环体中的相关代码。例如,以下代码:-
public static void main(String[] args) { int[] arr = {1,2,3}; for (int tmp :arr) { System.out.println(tmp); } } -
运行结果:
1
2
3 -
运行过程:
-
执行
tmp = arr[0],然后开始执行循环体 -
更新
tmp = arr[1],继续执行循环体 -
更新
tmp = arr[2],再次执行循环体 -
完成数组遍历后,循环结束
-
-
-
-
-
while循环-
public static void main(String[] args) { while (条件判断语句) { //循环体 } }-
当满足条件时,进入循环并重复执行,直到条件不再符合为止
-
示例:
-
public static void main(String[] args) { int left = 0; int right = 200; while (left++ < right--) { System.out.print(left + " "); } /*while (条件判断语句) { }*/ }功能说明:
该代码打印了 1 到 100 的所有数字(示例代码仅为演示,不具实际意义)
-
-
注意:
条件判断语句的返回值必须是布尔类型
-
-
-
do - while
-
do - while与while的主要区别在于,do - while无论条件是否成立都会先执行一次循环体,然后再进行条件判断,决定是否继续执行循环。后续操作与while一致,因此不做过多介绍 -
格式:
-
do{ //循环体 } while (条件判断语句); -
需要注意:
while后面需要加上分号;
-
-
-
-
-
跳转语句:
break、continue、return的语义区别-
break:立即终止当前的循环或switch语句,跳出所在的代码块。-
示例:
-
public static void main(String[] args) { for (int i = 0; i < 10; i++) { if (i == 5) { break; } System.out.printf("%d ",i); } }运行结果:
0 1 2 3 4
-
-
continue:跳过本次循环的剩余代码,直接进入下一次循环的条件判断-
示例:
-
public static void main(String[] args) { for (int i = 0; i < 10; i++) { if (i == 5) { continue; } System.out.printf("%d ",i); } } -
运行结果:
0 1 2 3 4 6 7 8 9
-
-
return:终止当前方法的执行,并返回一个值(如果方法有返回值)-
示例:
-
public static void main(String[] args) { for (int i = 0; i < 10; i++) { if (i == 5) { return; } System.out.printf("%d ",i); } }运行结果:
0 1 2 3 4
-
-
注意事项:
-
在
return、break和continue语句后,不能写后续代码,否则会编译报错。以下是三个常见的错误示例: -
public static void main(String[] args) { System.out.println(1); return; System.out.println(2);//编译报错 } -
public static void main(String[] args) { for (int i = 0; i < 5; i++) { if (i == 3) { break; System.out.println(i);//编译报错 } } } -
public static void main(String[] args) { for (int i = 0; i < 5; i++) { if (i == 3) { continue; System.out.println(i);//编译报错 } } }
-
-
五、数组
-
一维数组
-
一维数组的定义:
-
数组:数组是用于存储一类相同数据类型的元素,通过连续的内存空间和下标索引进行访问
-
一维数组的定义 有两种方法:
-
元素类型[] 数组名; 元素类型 数组名[];//不推荐
第一种方法更为常用,其优势在于表达更清晰,原因如下:
-
使用第二种方式定义时,按照从左到右的顺序,无法立即判断出这是一个数组
-
而使用第一种方法时,看到元素类型后的
[]符号,就能立即识别出这是一个数组
单独比较这两种定义方式:
-
在查看第二种定义方式时,虽然看到数组名后的
[]可以判断出这是一个数组,但无法立即知晓数组存储的数据类型 -
相比之下,第一种方法在显示
[]符号的同时,能够清晰地展示数组的元素类型
-
-
-
初始化
-
一维数组的初始化一般有以下几种方式:
-
静态初始化(简化版):
int[] arr1 = {1,2,3,4,5}; //创建了一个数组大小为5 内容为 1 2 3 4 5的数组 -
静态初始化(完整版本):
int[] arr2 = new int[] {1,2,3,4,5}; //创建了一个数组大小为5 内容为 1 2 3 4 5的数组 -
动态初始化
int[] arr3 = new int[5]; //创建了一个数组大小为5 内容都为0的数组
-
其在内存中分配情况如下:

-
-
一维数组的遍历
-
数组的访问是通过索引(下标)进行的
-
格式:数组名.[索引]
-
-
通过数组的下标,我们可以轻松遍历整个数组:
-
public static void main(String[] args) { int[] arr = {1,2,3,4,5,6,7,8,9,10}; for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } }运行结果:
1 2 3 4 5 6 7 8 9 10
-
-
-
简单算法练习(冒泡排序)
-
冒泡排序方法核心:
-
逐一比对数组中相邻两个元素的大小,如果
arr[0] > arr[1],则交换两者的位置 -
动画演示:

-
-
现有一个待排序的数组,编写一个冒泡排序方法对其进行排序:
-
import java.util.Arrays; public class Test1 { public static void mySort(int[] arr) { int len = arr.length; for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - i - 1; j++) { if (arr[j] > arr[j+1]) { int tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; } } } } public static void main(String[] args) { int[] arr = {1,2,9,3,8,4,7,5,6}; System.out.println("排序前:" + Arrays.toString(arr)); mySort(arr); System.out.println("排序后:" + Arrays.toString(arr)); } }
-
-
二维数组
-
二维数组本质上仍然是一维数组,它是用来存放一维数组的数组
-
定义格式:
元素类型[][] 数组名称;; 元素类型 数组名[][];//不推荐 -
示例
public static void main(String[] args) { int[] arr1 = {1,2,3}; int[] arr2 = {2,3,4}; int[] arr3 = {3,4,5}; int[][] arrS1 = {arr1,arr2,arr3}; int[][] arrS2 = {{1,2,3},{2,3,4},{3,4,5}}; System.out.println("arrS1:"); System.out.println(Arrays.toString(arrS1)); System.out.println(Arrays.deepToString(arrS1)); System.out.println("arrS2"); System.out.println(Arrays.toString(arrS2)); System.out.println(Arrays.deepToString(arrS2)); } -
运行结果:
arrS1:
[[I@4c873330, [I@119d7047, [I@776ec8df]
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
arrS2
[[I@3b07d329, [I@41629346, [I@404b9385]
[[1, 2, 3], [2, 3, 4], [3, 4, 5]] -
图解内存空间:

-
-
Arrays.toString()、Arrays.sort()工具类简介-
Arrays.toString()
-
使用
Arrays.toString()时需要导入相应的包:java.util.Arrays -
作用:
Arrays.toString()用于将一维数组转换为格式化的字符串-
格式:
[元素1, 元素2, ..., 元素N],每个元素之间使用逗号和空格分隔,且用方括号包围 -
支持类型:适用于所有基本数据类型和对象类型的一维数组
-
-
使用实例:
-
import java.util.Arrays;//使用Arrays.toString()需导入该包 public class Main { public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5}; String arrayString = Arrays.toString(numbers); System.out.println(arrayString); // 输出: [1, 2, 3, 4, 5] } }输出结果:
[1, 2, 3, 4, 5]
-
-
如果遇到二维数组,则需要使用
Arrays.deepToString()方法-
使用示例:
import java.util.Arrays;//使用Arrays.toString()需导入该包 public class Test { public static void main(String[] args) { int[][] numbers = {{1,2,3},{2,3,4},{3,4,5}}; System.out.println("Arrays.toString()"); System.out.println(Arrays.toString(numbers)); System.out.println("==========="); System.out.println("Arrays.deepToString()"); System.out.println(Arrays.deepToString(numbers)); } }输出结果:
Arrays.toString()
[[I@4c873330, [I@119d7047, [I@776ec8df]
===========
Arrays.deepToString()
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]通过输出可以看到,如果对二维数组使用
Arrays.toString(),则无法正确输出数组中的内容。相反,使用Arrays.deepToString()就可以正确地输出二维数组的所有内容
-
-
Arrays.sort()
-
作用:用于对一维数组进行快速排序。根据不同的数据类型,
Arrays.sort()会自动选择最适合的排序算法进行排序 -
使用示例:
-
public class Test3 { public static void main(String[] args) { int[] arr = {2,3,1,54,7,12,534,6}; String[] name = {"lis","Aril","Ashy","Nike","Peas"}; Arrays.sort(arr); System.out.println(Arrays.toString(arr)); Arrays.sort(name); System.out.println(Arrays.toString(name)); } }运行结果:
[1, 2, 3, 6, 7, 12, 54, 534]
[Aril, Ashy, Nike, Peas, lis] -
其次,
Comparable接口也可以实现自定义排序方式:假设有如下场景:
有一个group对象,该对象中存放了小组中每个人的姓名、年龄、性别等信息。如果要按照其中某一个属性进行排序,显然,使用默认的Arrays.sort()方法是不可取的。这时,我们就需要使用自定义的排序方式,例如:-
class Group implements Comparable<Group>{ // 需要自定义排序方式时 需要实现Comparable接口 // <>内代表的是需要对比的对象 String name; String sex; int age; public Group(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public int compareTo(Group o) { return this.age - o.age; }//重写 compareTo 方法 @Override public String toString() { return "Group{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", age=" + age + '}'; }//重写 toString 方法 } -
测试代码:
import java.util.Arrays; public class Test { public static void main(String[] args) { Group Lisa = new Group("Lisa","girl",18); Group Ail = new Group("Ail","girl",21); Group Alonzo = new Group("Alonzo","boy",20); Group[] groups = {Lisa,Ail,Alonzo}; Arrays.sort(groups); System.out.println(Arrays.toString(groups)); } } class Group implements Comparable<Group>{ // 需要自定义排序方式时 需要实现Comparable接口 // <>内代表的是需要对比的对象 String name; String sex; int age; public Group(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } @Override public int compareTo(Group o) { return this.age - o.age; }//重写 compareTo 方法 @Override public String toString() { return "Group{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", age=" + age + '}'; } }运行结果:
[Group{name='Lisa', sex='girl', age=18}, Group{name='Alonzo', sex='boy', age=20}, Group{name='Ail', sex='girl', age=21}] -
注意事项:
-
如果需要实现自定义排序规则,则必须实现
Comparable接口 -
在
Comparable后面的<>中,填写需要比较的类型 -
在重写的
compareTo方法中,决定了排序方式:是按升序还是降序排列
-
-
-
六、方法的使用与机制
-
方法的定义、调用、参数传递规则
-
方法的定义
-
格式:
访问权限 返回类型 方法名称(参数) { //方法体 }
-
-
方法的传参
-
在括号
()内填写需要传入的参数,这里的参数称为形参,它是实参的一份临时拷贝。 -
示例代码:
class Test{ public void instance(int tmp) { tmp = 10; } } public class Test2 { public static void main(String[] args) { Test test = new Test(); int a = 20; test.instance(a); System.out.println(a); } }该代码中,最终输出仍然是 20。这说明调用
instance方法并没有改变变量a的值。原因在于,方法中改变的是形参tmp,而不是实参a。可以将其类比为:你有一份卷子 A,你将它复印了一份,然后在复印件上做了标记。无论你如何修改复印件,原始的卷子 A 都不会受到影响。
那么,计算机内部是如何处理这种情况的呢?
下面是一个图解:

-
-
普通方法
-
普通方法的调用
-
外部调用(外部类或
main中调用):需要先实例化对应的类,然后通过实例使用点运算符(.)来调用普通方法。
例如:obj.method()。 -
内部调用(同一类内调用):在同一类内部,实例方法可以直接调用本类的其他实例方法(无需实例引用)。
但如果调用方法所在的上下文是static方法,则 不能直接调用实例方法,必须通过实例引用来调用(或把被调用的方法也声明为static,但要注意语义是否合适)。
-
-
示例:
class Greet{ public void Greet() { System.out.println("hello word"); } } public class Test1 { public static void main(String[] args) { Greet greet = new Greet(); greet.Greet(); } }
-
-
静态方法
-
静态方法定义时需要使用关键字
static -
格式:
访问权限 static 返回类型 方法名称() { //方法体 } - 静态方法的调用
- 外部调用(类外):使用
类名.方法名()进行调用。 -
内部调用(同一类内):
-
在静态方法内部可以直接调用同类的其他静态方法(无需类名)。
-
在实例方法内部也可以直接调用静态方法(静态方法属于类级别,对实例可见)。
-
注意:静态方法不能直接访问实例变量或实例方法,如需访问必须通过对象引用。
-
- 外部调用(类外):使用
- 示例:
class Test { public static void Tests() { System.out.println("Tests()"); } public static void test2() { Tests(); } } public class Test1 { public static void main(String[] args) { Test.Tests(); Test.test2(); } }运行结果:
Tests()
Tests()
-
-
-
静态方法与实例成员访问的简易示意说明:
-
静态方法属于类级别,它在内存中只有一份,所有实例共享。
-
实例成员属于对象级别,每个对象有自己的独立拷贝。
-
因此,静态方法无法直接访问对象的实例成员(因为它不知道是哪个对象的成员),只能通过明确的对象引用来访问。
-
实例方法属于某个对象,可以访问该对象的所有成员(静态的和实例的)。
-
-
返回值与
void-
方法的返回值
-
先通过一段代码来了解什么是返回值以及它的作用:
public class Test2 { public static int Add(int a,int b) { return a+b; } public static void main(String[] args) { int a = 10; int b = 20; System.out.println(Add(a, b)); } }运行结果:
30 -
通过这段代码可以看到,
main方法调用了Add函数,并将变量a和b的值传递给它。此时,Add方法中的a和b是形参。计算完成后,
Add方法如何将a + b的结果告诉main方法呢?答案是:通过
return关键字将计算结果返回给调用者。也就是说,调用该方法的地方(这里是
main)会接收到return语句后返回的值。 -
图解(仅为直观示意,不代表真实内存结构):

-
-
void
-
void关键字表示方法没有返回值,即方法执行完毕后不需要向调用者返回任何数据。简单来说,使用
void修饰的方法不通过return语句返回值,只能执行方法体内的操作(比如打印、修改对象状态等) -
示例:
public class Demo { // 一个没有返回值的方法,直接打印内容 public static void printMessage() { System.out.println("这是一个void方法,没有返回值"); } public static void main(String[] args) { printMessage(); // 调用void方法,不接收任何返回值 } }在调用
void方法时,不能将它作为表达式的一部分,也不能用变量接收它的返回值,否则编译会报错。
-
-
-
方法重载(Overload)概念与实战例子
-
概念:当调用同一个方法的时候,对于不同的参数可以使用不同的运行逻辑。
方法重载指的是在同一个类中,允许多个方法名称相同,但参数列表不同(参数个数或类型顺序不同),以实现根据不同参数执行不同逻辑的一种机制。
换句话说,调用同一个方法名时,根据传入参数的不同,Java会自动选择匹配的方法执行。
-
为什么需要方法重载?
假设我们要实现以下需求:
-
计算两个整数的和
-
计算三个浮点数的和
-
计算两个浮点数的
-
计算三个整数的和
-
-
如果为每种情况都写一个不同名字的方法,会导致方法名臃肿、调用不方便。
方法重载则可以让我们只用一个方法名,通过不同的参数组合实现多种计算逻辑,代码更加简洁清晰。
-
方法重载的实现条件
-
方法名必须相同。
-
参数列表不同,包括:
-
参数个数不同;
-
或参数类型顺序不同(需要注意:当参数类型完全相同且数量为2时,交换参数顺序不构成重载,编译器会报错;只有参数个数≥3且顺序不同才算有效重载)。
-
-
方法必须定义在同一个类中。
-
示例:
public class Test3 { public static int Add(int x,int y) { return x+y; } public static double Add(int x,int y,double d) { return x+y+d; } public static double Add(int x,double d,int y) { return x+d+y; } public static int Add(int x,int y,int z) { return x+y+z; } public static void main(String[] args) { int x = 10; int y = 20; double d = 41.1; int z = 30; System.out.println(Add(x, y)); System.out.println(Add(x, y, d)); System.out.println(Add(x, d, y)); System.out.println(Add(x, y, z)); } }运行结果:
30
71.1
71.1
60
-
-
static方法与普通方法的区别区别 static 方法 普通方法 访问权限 只能访问类的静态成员,不能访问非静态成员 可以访问类中的所有成员(静态和非静态) 调用方式不同 通过类名直接调用 通过实例化对象进行调用 关键字区别 需要使用static关键字修饰方法 不需要使用static进行修饰 所属关系不同 属于类 属于实例对象 声明周期不同 随类的加载而存在,整个程序运行期间只有一份 随对象的创建和销毁而存在
七、面向对象
-
类与对象的定义与使用
-
类的定义
类是对一类事物的抽象描述,是具有相同属性和行为的对象的集合,它定义了对象的属性(成员变量),行为(成员方法) -
格式:
修饰符 class 类名 { // 成员变量(属性) // 成员方法(行为) } -
对象的定义:
对象是类的实体,可以通过对象访问类中定义的属性和方法 -
对象的创建:
通过 new 关键字创建对象
格式:类名 对象名 = new 类名(); -
类与对象的关系
-
类是抽象的模板,用来描述事物的共性和特征
-
对象是类的具体实例,拥有类中定义的成员属性和成员方法
-
可以将类看做是一个模板,而对象通过该模板进行创建,创建的内容必须包含目标中的内容,模板以外的内容自由发挥
-
-
-
成员变量 vs 局部变量
-
成员变量是定义在类中的变量,属于对象或类本身。
-
局部变量是定义在方法、构造器或代码块中的变量,只在该范围内有效。
-
初始化要求
成员变量不需要显式初始化,系统会自动赋予默认值(如整型默认是0,布尔型是false)。
局部变量必须显式初始化后才能使用,否则编译器会报错。
生命周期
成员变量随着对象的生命周期存在,直到对象被销毁才释放。
局部变量随着所在方法或代码块的执行结束而销毁,生命周期较短。
作用范围
成员变量的作用域是整个类内,可以被类中所有非静态方法访问。
局部变量的作用域仅限于声明它的方法或代码块内部。
-
构造方法:默认构造器、自定义构造器
-
构造方法一般是用于进行成员的初始化
-
格式如下:
-
public 类名(参数) { //进行成员的初始化 }
-
-
示例:
class A { private int age; private String name; public A(int age, String name) { this.age = age; this.name = name; } }在编译器中,通常会提供快速生成构造方法的功能。
以 IntelliJ IDEA 2022.1.4 为例,可以通过 Alt + Insert 快捷键唤出代码生成窗口,如下所示:

-
按照上述步骤在窗口中进行操作后,编译器会自动生成对应的构造方法,无需手动编写,大幅提升编码效率。
-
-
this关键字的实际用途-
当方法的参数名与类中的成员变量名发生冲突时,可以使用
this.成员名来明确指代类的成员变量。
这里的this代表当前对象的引用,this.name表示 “当前对象的 name 成员变量”。 -
示例:
public class Person { String name; public Person(String name) { this.name = name; // this.name 表示成员变量,右侧 name 表示构造方法的参数 } public void showName() { System.out.println("我的名字是:" + this.name); } }
-
-
封装与访问修饰符
-
封装是面向对象的基本原则之一,指将类的属性和实现细节对外隐藏,只通过受控的接口(方法)暴露行为。
打个比方:计算器对外只提供按键和显示屏,我们看不到内部的实现细节,这就是封装的思想。 -
在进行封装之前需要了解访问修饰限定符
-
访问修饰限定符 关键字 范围 访问权限(1-4,数字越大,越开放) 同一包中的同一类 同一包中的不同类 不同包中的子类 不同包中的非子类 private √ × × × 1 default √ √ × × 2 protected √ √ √ × 3 public √ √ √ √ 4 -
一般情况下成员变量用
private,对外接口(方法)用public。-
格式:
访问修饰限定符 数据类型 成员名称;
-
-
下列是一个使用封装 定义的计算器类 的示例
class Calm { private int num1; private int num2; public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } public int Add() { return this.num1 + this.num2; } public int Sub() { return this.num1 - this.num2; } public int Mul() { return this.num1 * this.num2; } public double Div() { return (double)this.num1 / (double)this.num2; } } public class Test3 { public static void main(String[] args) { Calm calm = new Calm(); calm.setNum1(10); calm.setNum2(20); System.out.println("加法 " + calm.Add()); System.out.println("减法 " + calm.Sub()); System.out.println("除法 " + calm.Div()); System.out.println("乘法 " + calm.Mul()); } }运行结果:
加法 30
减法 -10
除法 0.5
乘法 200
-
-
继承(Inheritance)
-
继承是面向对象编程中的三大特性之一,用于描述类与类之间的“是一个”(is-a)关系。
通过继承,子类可以获得父类的属性与方法,并且可以在此基础上进行扩展或修改。 -
基本概念:
继承用于描述子类与父类之间的行为和属性关系。
例如,现有"猫"、"狗"、"鸟"等类,它们都属于"动物"这个父类。因此,"动物"类可以定义这些子类共有的属性和行为,比如它们都拥有"年龄"属性,并且都能执行"进食"行为。
-
想要实现继承需要使用关键字extends
-
格式:
访问修饰限定符 子类类名 extends 父类类名 { // 成员属性 // 成员行为 }
-
-
实现继承的几个注意事项:
-
如果父类有显示构造方法,其子类必须重写父类的显示构造方法
class Animal { String name; int age; public Animal(String name,int age) { this.name = name;//父类构造方法 this.age = age; } } class Dog extends Animal { public Dog(String name,int age) { super(name,age);//实现父类的构造方法 如不进行实现编译器会报错 } } -
如果父类为抽象类,且含有抽象方法,那么子类必须实现父类的抽象方法
abstract class Animal { String name; int age; abstract public void eat();//父类的抽象方法 } class Dog extends Animal { /* public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return this.name; } public int getAge() { return this.age; } */ public void eat() { // 子类实现 抽象方法 System.out.println(this.name + " 正在吃狗粮"); } } -
Java只支持单继承,如果想要实现多继承 需要通过接口进行实现
-
-
方法重写(Override)与 方法重载(Overload)区别
-
方法重写:
-
发生方法重写的前提:
-
必须在继承体系中(父类和子类的关系)。
-
方法名相同。
-
参数列表必须完全一致(包括顺序和类型)。
-
返回值类型必须相同或是父类返回值类型的子类型(协变返回类型)。
-
访问权限不能比父类更严格(可以更宽松)。
-
子类方法不能抛出比父类方法更多的检查型异常
-
-
方法重写一般发生在多肽下
-
方法重写的本质是动态绑定
-
所谓动态绑定,就是在程序运行过程中,才知道调用哪一个方法,在编译期间是无法知晓的
-
那么什么是动态绑定呢?
-
动态绑定一般发生在多肽下
-
假设现有一个父类Animal,其子类包含Dog和Bird,且它们三者都定义了eat方法。
当通过Dog对象调用父类的eat方法时,程序会将父类的eat方法与Dog类中的eat方法进行绑定,此时调用的就是Dog类重写后的eat方法。
同样,当通过Bird对象调用父类的eat方法时,父类的eat方法会与Bird类的eat方法绑定,此时调用的就是Bird类重写后的eat方法。这就是所谓的动态绑定,它是在程序运行时确定调用哪个具体方法的机制,是多态的核心体现。
-
-
方法重载与方法重写的区别
区别 方法重写 方法重载 发生位置 发生在继承体系下 发生在同一类中 参数列表要求 参数列表必须与父类一致 参数列表需不同 绑定方式 发生动态绑定 发生静态绑定 返回类型限制 返回类型必须与父类方法相同或是其子类型(协变返回类型) 无限制 访问权限限制 子类方法访问权限不能比父类方法更严格(可放宽或保持一致) 无限制 -
一个实例:
-
class Animal { String name; public void eat() { System.out.println("吃"); } public Animal(String name) { this.name = name; } } class Dog extends Animal { public Dog(String name) { super(name); } public void eat() { System.out.println(this.name + " 正在吃狗粮"); } } class Bird extends Animal { public Bird(String name) { super(name); } public void eat() { System.out.println(this.name + " 正在吃鸟粮"); } } public class Test2 { public static void eatAnimals(Animal animal) { animal.eat(); } public static void main(String[] args) { Dog dog = new Dog("小黄"); Bird bird = new Bird("小翠"); eatAnimals(dog); eatAnimals(bird); } }运行结果:
小黄 正在吃狗粮
小翠 正在吃鸟粮 -
回看 main 方法,调用了 eatAnimals 方法,虽然该方法中都是通过 Animal 类型 来调用 eat 方法,但输出结果却不相同。这正体现了 方法重写 与 动态绑定 的效果。
-
-
super关键字-
用于将子类的参数传递给父类的构造方法或调用父类的方法。
-
-
多态
-
概念:通俗来说,就是不同人去干同一件事情会发生不同的结果
-
多态发生的前提:
-
必须发生在继承体系下
-
父类与子类必须发生方法重写
-
发生动态绑定
-
-
一个实例:
class Animal { String name; public void eat() { System.out.println("吃"); } public Animal(String name) { this.name = name; } } class Dog extends Animal { public Dog(String name) { super(name); } public void eat() { System.out.println(this.name + " 正在吃狗粮"); } } class Bird extends Animal { public Bird(String name) { super(name); } public void eat() { System.out.println(this.name + " 正在吃鸟粮"); } } public class Test2 { public static void eatAnimals(Animal animal) { animal.eat(); } public static void main(String[] args) { Dog dog = new Dog("小黄"); Bird bird = new Bird("小翠"); eatAnimals(dog); eatAnimals(bird); } }
-
-
运行结果:
小黄 正在吃狗粮
小翠 正在吃鸟粮 -
回看 main 方法,调用了 eatAnimals 方法,虽然该方法中都是通过 Animal 类型 来调用 eat 方法,但输出结果却不相同。这正体现了 方法重写 与 动态绑定 的效果。
八、接口
-
什么是接口(Interface)
-
接口(Interface)是Java中一种特殊的抽象类型,用来定义一组方法的规范,但不包含具体实现。简单来说,接口就像一个“合同”,规定了某些方法的名字、参数和返回类型,但不写具体的代码细节,具体怎么做由实现这个接口的类来完成。
-
-
接口和类的区别
-
接口和类的区别 方面 类 接口 定义 描述对象的属性与行为 定义一组方法规范 关键字 class interface 方法 可以有具体实现 只能声明抽象方法 变量 可以有实例变量 只能有常量 实例化 可以创建对象 不可以进行实例化 继承 只支持单继承 支持多继承 实现 直接定义具体功能 定义方法,由其他类进行实现 目的 实现具体功能 提供接口、规范行为、定义标准 多肽体现 通过父类引用 通过接口引用 -
接口相当于加了一层检验,并规范化了许多方法的类
-
简单来说:
-
类是具体的,能实现方法并创建对象;
-
接口是抽象的,只定义规则,不能直接用,要由类去实现。
-
-
-
接口的作用
-
规范行为:定义一组方法,保证实现接口的类都必须提供这些方法,形成统一标准。
-
实现多态:接口类型可以指向不同实现类的对象,方便代码扩展和维护。
-
支持多继承:Java类只能单继承,但接口可以多继承,方便功能组合。
-
2. 接口的定义和语法
-
如何定义接口(
interface关键字)-
public interface 接口名 { // 常量(public static final,可以省略修饰符) 数据类型 常量名 = 值; // 抽象方法(public abstract,可以省略修饰符) 返回类型 方法名(参数列表); }
-
-
接口中方法的默认特性(默认为抽象方法,无方法体)
public interface MyInterface { // 常量 int CONSTANT = 100; // 抽象方法 void method1(); int method2(String param); }- 成员方法
- 接口中的成员方法默认被public abstract进行修饰,默认为抽象方法,但可以通过static 关键字使其变为静态方法
- 成员属性
- 接口中的成员属性默认被public static final所修饰,且为常量
- 成员方法
3. 实现接口(implements)
-
类如何实现接口
-
使用
implements关键字来声明一个类实现某个接口。 -
类必须实现接口中所有的抽象方法,否则该类要声明为
abstract。-
如果后续有类继承了该类,那么后续的类必须实现还未实现的所有抽象方法
-
若后续类继承该类,则必须实现所有未完成的抽象方法
-
-
-
一个类可以实现多个接口
-
其格式如下:
class 类名 implements 接口1,接口2,接口3…… { // 成员方法 属性 } - 通过接口,便可以实现多继承的效果
-
注意事项:
接口不可以被实例化。接口是Java中一种完全抽象的类,它只包含抽象方法(在Java 8之后也可以包含默认方法和静态方法)和常量。接口的主要作用是定义一组规范或契约,由实现类来具体实现这些方法。由于接口本身没有具体实现,所以不能直接使用new关键字创建实例。
以下代码尝试实例化接口,但会导致编译错误。
public interface Animal {
void eat();
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal(); // 编译错误:Animal是抽象的,不能被实例化
}
}
the end *
3045

被折叠的 条评论
为什么被折叠?



