Java基础学习笔记
一、Java 三大版本
JavaSE:标准版(桌面程序、控制台开发...)
JavaEE:企业级开发(web端、服务器开发...)
JavaME:嵌入式开发(手机、家电...)
二、Java 语言特点
1. 计算机语言类型有哪些?
根据 执行方式 的不同,计算机语言大致可以分为 编译型语言、解释型语言
1.编译型语言:编译型语言执行的时候,CPU可直接读取可执行代码(机器语言),速度快;但是要根据不同CPU安装对应编译器,还需要根据不同操作系统选用应不同启动代码,不便利。
例如:C、C++、Delphi等;其中选取C语言为代表来说明,C编程的基本策略是,用程序把源代码文件转换为可执行文件。
2.解释型语言:解释型语言执行的时候,需要解释器翻译一行,CPU执行一行,速度相对较慢;仅需要根据不同操作系统安装对应解释器,十分便利。
例如:Python、JavaScript、Perl、Shell等;其中选取Python为代表来说明,Python编程的基本策略是,用程序把源代码文件一行一行地转换为可执行代码。
根据 编程方式 的不同,计算机语言大致可以分为 面向过程语言、面向对象语言、函数式语言
1.面向过程语言:以 “过程”(函数)为核心,按步骤解决问题,强调代码的顺序执行。
例如:C、Fortran等;
2.面向对象语言:以 “对象” 为核心,通过封装、继承、多态组织代码,适合复杂系统开发。
例如:C#、Java、Ruby等;
3.函数式语言:以 “函数” 为基本单元,强调纯函数(无副作用)和不可变数据,适合并行计算。
例如:Scala、Haskell、Lisp等;
2. Java属于哪种类型的计算机语言?
Java属于半解释半编译型、面向对象型 的语言。
通过Java程序运行流程可分析出半解释半编译的特性;通过Java程序的编程方式可知面向对象的特性。
3. Java程序运行流程
1.源代码(.java)通过Java编译器编译成字节码文件(.class)
// 通过javac命令将java源文件(.java)编译为字节码文件(.class)
javac HelloWorld.java
2.字节码文件(.class)通过Java虚拟机(JVM)执行;
首先Java虚拟机(JVM)通过类加载器加载字节码文件(.class),其次通过字节码校验器进行校验,保证数据安全性,然后将字节码文件(.class)通过解释器和即时编译器(JIT)二者双管齐下(半解释半编译)生成机器码,其中解释器负责解释执行,即时编译器(JIT)负责将热代码编译为本地机器码并缓存,用以提升效率,最后将机器码输出给操作系统(OS)。
// 通过java命令解释并执行字节码文件(.class)
java HelloWorld
Tips:即时编译技术(JIT)在Java1.2版本中首次被引入,标志着Java从纯解释执行向半解释半编译执行模式的转型。Java虚拟机(JVM)通过即时编译技术(JIT)将热点代码编译为本地机器码,提升了执行效率,使Java拥有接近编译型语言的性能。
Java程序运行流程图例:
4. Java语言优缺点
优点:
1.跨平台性:Java代码(.java)编译为字节码(.class),由Java虚拟机(JVM)解释执行,屏蔽了不同操作系统的底层差异。在不同平台安装其对应的Java虚拟机(JVM),即可实现“一次编写,到处运行”。一次开发可在Windows、Linux、macOS等多个平台运行,适合分布式系统和云服务。
2.面向对象设计:封装、继承、多态,支持复杂软件架构设计(如 MVC 模式)。代码可复用性高、可维护性强,适合大型团队协作开发。
3.强大的生态系统:提供丰富的API(如集合框架、多线程、网络编程)。第三方框架有Spring(企业开发)、Hibernate(ORM)、JUnit(测试)等,加速开发周期。
4.自动内存管理(GC 垃圾回收机制):JVM自动回收不再使用的对象内存,减少内存泄漏风险。相比C/C++的手动内存管理,开发效率更高,降低调试成本。
5.高性能:频繁执行的热点代码由JVM中的即时编译技术(JIT)即时编译为机器码,接近原生性能。
6.安全性:"字节码校验"JVM在加载字节码时进行安全检查,防止恶意代码。"沙箱机制"限制程序访问系统资源,适合Web应用和移动开发。
缺点:
1.语法复杂:需要编写大量样板代码(如getter/setter、异常处理)。
2.程序迭代速度较慢:大型项目编译耗时较长,相比脚本语言(如 Python)迭代速度慢。
三、JDK、JRE、JVM
JDK(Java Development Kit):Java开发环境,整个Java的核心,包含了Java开发工具和JRE。
JRE(Java Runtime Environment):Java运行环境,包含虚拟机Java基础类库和Java虚拟机(JVM)。
JVM(Java Virtual Machine):Java虚拟机,源代码(.java)编译成字节码文件(.class)后,字节码文件(.class)在JVM上运行。
四、Java关键字
1. 基本概念
Java 关键字(Keywords)是编程语言中被赋予特殊含义的单词,开发者不能将它们用作变量名、类名或方法名等标识符。
2. 关键字有哪些?
abstract、assert、boolean、break、byte、case、catch、char、class、const、continue、default、do、double、else、enum、extends、final、finally、float、for、goto、if、implements、import、instanceof、int、interface、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while
Tips:具体含义跟随后续学习深入了解。
五、Java标识符
1. 基本概念
在 Java 中,标识符(Identifier)是用于给类、变量、方法、接口、包等命名的字符序列。它是程序员自定义的名称,用于唯一标识一个编程元素。
2. 命名规则
-
只能由 字母(
A-Z/a-z)、数字(0-9)、下划线(_)、美元符号($)组成,不能包含空格、标点符号(如#、%、&等)或其他特殊字符。
示例:myVar、_name、$value、user123是合法的;my-var(特殊字符)、123abc(以数字开头)、class(关键字)是非法的。 -
不能以数字开头,标识符必须以 字母(
A-Z/a-z)、下划线(_)或美元符号($)开头。
示例:a1合法,1a非法。 -
不能使用 Java 关键字(如
class、public、static等)或保留字(如goto、const)作为标识符。
示例:int(关键字)、goto(保留字)均是非法的。 -
区分大小写,Java是大小写敏感的语言,因此
MyClass和myclass是两个不同的标识符。
示例:变量age和Age被视为不同变量。
3. 命名规范
-
驼峰命名法(Camel Case)
- 类名、接口名:每个单词首字母大写(大驼峰,Upper Camel Case)。
示例:StudentInfo、UserService。 - 变量名、方法名:首单词首字母小写,后续单词首字母大写(小驼峰,Lower Camel Case)。
示例:userName、getAge()。
- 类名、接口名:每个单词首字母大写(大驼峰,Upper Camel Case)。
-
包名:全小写,通常用公司域名倒置(避免命名冲突)。
示例:com.xu.liu。 -
常量名:全大写,单词间用下划线分隔。
示例:MAX_SIZE、DEFAULT_VALUE。 -
避免使用单字符命名(除临时变量或循环变量),推荐用有意义的名称(如
count比c更易读)。 -
不建议使用
$和_,虽然技术上合法,但$常用于自动生成的代码(如内部类),_在 Java 9 后被保留为关键字(用作无效标识符),因此应尽量避免在自定义标识符中使用。
六、Java数据类型
1.变量
基本概念:在Java中,变量是存储数据的容器,用于存储和操作数据,由数据类型、变量名、值三部分组成。
String name = '刘旭涛';
Tips:
- 声明变量时必须指定类型,且变量只能存储该类型的值
- 变量的作用域决定变量的可见性,应避免命名冲突。
2. 数据类型
Java数据类型分为两大类:基本数据类型、引用数据类型
基本数据类型(四类八种):
整数型:byte、short、int、long
浮点型:float、double
字符型:char
布尔型:boolean
引用数据类型:
除 基本数据类型 以外的,都称之为 引用数据类型
图示:
3. 数据类型转换
Java数据类型转换分为两种:自动类型转换(隐式转换)、强制类型转换(显式转换)
自动类型转换(隐式转换):当小范围数据类型赋值给大范围数据类型时,Java 会自动转换。
强制类型转换(显式转换):当大范围数据类型赋值给小范围数据类型时,需要手动显式转换,可能导致数据精度丢失。
Tips:
1.范围从小到大:byte → short → int → long → float → double
2.字符型参与时:char → int → long → float → double
3.布尔型:boolean 不可与其他类型转换
注意事项:
1.数据溢出:强制转换时可能导致数据溢出(如 int 转 byte)。
2.精度丢失:浮点数转整数时会截断小数部分。
3.类型兼容性:boolean 不可与其他类型转换。
4.字符串转换异常:若字符串格式不合法(如 "abc" 转 int),会抛出 NumberFormatException
示例:
------------------------------自动类型转换(隐式转换)------------------------------
// 整数类型转换
int numInt = 100;
long numLong = numInt; // int → long(自动转换)
// 整数到浮点数转换
float numFloat = numInt; // int → float(自动转换)
// 字符到整数转换
char ch = 'A';
int ascii = ch; // char → int(自动转换),结果为 65
// 不同类型运算时的自动转换
int a = 10;
double b = 3.5;
double result = a + b; // int → double(自动转换)
------------------------------强制类型转换(显式转换)------------------------------
// 浮点数到整数的强制转换(精度丢失)
double numDouble = 3.14;
int numInt = (int) numDouble; // 结果为 3(小数部分被截断)
// 大范围整数到小范围整数的强制转换(数据溢出)
long bigNum = 2147483648L; // 超过 int 最大值
int smallNum = (int) bigNum; // 结果为 -2147483648(数据溢出)
// 字符与整数的强制转换
int ascii = 97;
char ch = (char) ascii; // 结果为 'a'
七、Java运算符
1. 算数运算符
在Java中,算术运算符有:加+、减-、乘*、除/、取余%、自增++、自减--
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 5 + 2 | 7 |
- | 减法 | 5 - 2 | 3 |
\* | 乘法 | 5 * 2 | 10 |
/ | 除法 | 5 / 2 | 2(整数相除取整) |
% | 取余 | 5 % 2 | 1 |
++ | 自增(前缀 / 后缀) | int a=5; ++a; int b=2; b++; | a 变为 6 b 变为 3 |
-- | 自减(前缀 / 后缀) | int a=5; --a; int b=2; --b; | a 变为 4 b 变为 1 |
Tips(关于浮点型数据的除和取余):
1.浮点型数据进行除/、取余%运算时,除数可以为0,不会抛出异常,但会产生特殊值。
- 正数除以或取余 0:结果为Infinity(正无穷大)
- 负数除以或取余 0:结果为-Infinity(负无穷大)
- 零除以或取余 0:结果为NaN(Not a Number,非数字)
2.浮点型数据进行除/、取余%运算时,结果值可以是小数。
- 除以:浮点型数据进行除/运算时不取整
- 取余:取余%运算本身就可能余小数
3.浮点型数据进行除/、取余%运算时,结果值的符号规则为:
- 除以:同号为正,异号为负
- 取余:与被除数符号一致
4.浮点型数据永远存在精度问题,在Java中推荐使用BigDecimal处理高精度场景。
Tips(关于自增自减):
1.自增自减运算时,分为前缀和后缀两种使用方式:
- 前缀:先将变量的值 加/减 1,然后再使用 增加/减少 后的值进行运算
- 后缀:先使用变量原来的值进行运算,运算结束后再将变量的值 加/减 1
int a = 5;
int b = ++a; // 先将a加1(a变为6),再将6赋值给b
System.out.println("a = " + a); // 输出:a = 6
System.out.println("b = " + b); // 输出:b = 6
int c = 5;
int d = c++; // 先将5赋值给d,然后c再加1(c变为6)
System.out.println("c = " + c); // 输出:c = 6
System.out.println("d = " + d); // 输出:d = 5
2.在各种运算符的组合使用中,自增自减运算符优先级最高!
int a = 5;
int result = ++a + 3; // 先将a加1(a=6),再计算6+3=9
System.out.println("result = " + result); // 输出:9
System.out.println("a = " + a); // 输出:6
int b = 5;
int result = b++ + 3; // 先计算5+3=8,再将b加1(b=6)
System.out.println("result = " + result); // 输出:8
System.out.println("b = " + b); // 输出:6
3.自增自减赋值给原变量时的问题讨论与分析,先说结论:
- 前缀自增自减原变量值改变(会加减1)
- 后缀自增自减原变量值不变(不会加减1)
int x = 8;
x = ++x;// 先对原变量x进行加减运算(x变为9),再将x的值(9)复制出来,最后原变量x又指向了复制出来的 9
System.out.println(x);// 输出 9
int y = 6;
y = y++;// 先将y的值(6)复制出来,再对原变量y进行加减运算(x变为7),最后原变量y又指向了复制出来的 6
System.out.println(y);// 输出 6
4.总结自增自减参与运算时的运算流程!!!
- 前缀:先将原变量进行加减运算,此时原变量的值已经改变,再将运算后原变量的值复制出来,最后复制出来值参与运算(赋值=也是运算行为,属于赋值运算符)
- 后缀:先将原变量的值复制出来,再将原变量进行加减运算,此时原变量也已经改变,但是最后参与运算的是之前复制出来的值(赋值=也是运算行为,属于赋值运算符)
// 前缀运算流程分析,示例代码如下:
int x = 8;
x = x + 1;// 先将原变量进行加减运算,此时原变量的值已经改变
int temp = x;// 再将运算后原变量的值复制出来,最后复制出来值参与运算
// 后缀运算流程分析,示例代码如下:
int x = 8;
int temp = x;// 先将原变量的值复制出来
x = x + 1;// 再将原变量进行加减运算,此时原变量也已经改变,但是最后参与运算的是之前复制出来的值
5.建议在代码中清晰地使用自增自减,以提高代码可读性,避免在复杂表达式中混用前缀和后缀形式。
2. 赋值运算符
在Java中,赋值运算符有:等于=、加等于+=、减等于-=、乘等于*=、除等于/=、取余等于%=
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
= | 等于 | int a = 5; a = 2; | a 变为 2 |
+= | 加等于 | int a = 5; a += 2; | a 变为 7 |
-= | 减等于 | int a = 5; a -= 2; | a 变为 3 |
*= | 乘等于 | int a = 5; a *= 2; | a 变为 10 |
/= | 除等于 | int a = 5; a /= 2; | a 变为 2 |
%= | 取余等于 | int a = 5; a %= 2; | a 变为 1 |
Tips:
1.赋值运算符会自动进行类型转换
byte b1 = 5;
b1 += 2;// 这里2是默认int类型,运算时会自动处理类型转换,不会出现编译错误,等价于 b1 = (byte) (b1 + 2);
2.赋值运算符会先计算右侧表达式的值,再进行赋值操作
int x = 5;
x *= 2 + 3; // 先计算2+3得5,再计算x*5,结果为25
3. 关系运算符
在Java中,关系运算符有:等等于==、不等于!=、大于>、小于<、大于等于>=、小于等于<=
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
== | 等等于 | 5 == 2 | false |
!= | 不等于 | 5 != 2 | true |
> | 大于 | 5 > 2 | true |
< | 小于 | 5 < 2 | false |
>= | 大于等于 | 5 >= 2 | true |
<= | 小于等于 | 5 <= 2 | false |
Tips:
1.关系表达式的运算结果必然是布尔类型,也就是true或false
2.关系运算符经常会在条件语句和循环语句中被使用
// if-else语句
int number = 10;
if (number > 0) {
System.out.println("正数");
} else {
System.out.println("非正数");
}
// for循环
for (int i = 0; i <= 5; i++) {
System.out.print(i + " "); // 输出:0 1 2 3 4 5
}
4. 逻辑运算符
在Java中,逻辑运算符有:与&、或|、短路与&&、短路或||、非!、异或^
-
与
&:只有当两边表达式都为true时,结果才是true,且两边的表达式都会执行。 -
短路与
&&:只有当两边表达式都为true时,结果才是true,左边为true时,右边不执行。 -
与
|:只要两边表达式中有一个为true,结果就为true,且两边的表达式都会执行。 -
短路与
||:只有当两边表达式都为true时,结果才是true,左边为true时,右边不执行。 -
非
!:对单个表达式的布尔值进行取反。 -
异或
^:判断两边表达式是否不同,当两边表达式的布尔值不同时,结果为true;相同时,结果则为false。
// 与& 示例
int x = 5;
System.out.println(false & (x++ > 10)); // 输出false
System.out.println(x); // 输出6,说明x++执行了
// 或| 示例
int x = 5;
System.out.println(true | (x++ > 10)); // 输出true
System.out.println(x); // 输出6,意味着x++执行了
// 短路与&& 示例
int x = 5;
System.out.println(false && (x++ > 10)); // 输出false
System.out.println(x); // 输出5,说明x++未执行
// 短路或|| 示例
int x = 5;
System.out.println(true || (x++ > 10)); // 输出true
System.out.println(x); // 输出5,表明x++未执行
// 非! 示例
boolean a = true;
System.out.println(!a); // 输出false
// 异或^ 示例
boolean a = true;
boolean b = false;
System.out.println(a ^ b); // 输出true
boolean c = true;
boolean d = true;
System.out.println(c ^ d); // 输出false
boolean e = false;
boolean f = false;
System.out.println(e ^ f); // 输出false
5. 位运算符
6. 三元运算符
在Java中,三元运算符的写法是:布尔表达式 ? 表达式1 : 表达式2
int score = 85;// 成绩分数为85
String result = (flag >= 60) ? "及格" : "不及格";// 如果大于等于60分为及格,否则就不及格
Tips:
1.表达式1和表达式2的返回数据类型必须相同
2.三元表达式的执行顺序为:先执行布尔表达式,再执行对应的表达式,另外一个表达式不执行
int value1 = 5;
int value2 = 2;
int result = value1++ > 1 ? value1 : value2++;
System.out.println(value1);// 6
System.out.println(value2);// 2
System.out.println(result);// 6
八、流程结构
1. 顺序结构
Java 的基本流程结构就是顺序结构,可以理解为 "代码从上至下一行一行执行";
2. 选择结构
在Java中,选择结构的语句有:if-else、switch
// if语句基本写法("bol"必须为boolean数据类型,且当"bol"为true时,大括号内的代码才执行;否则直接跳过本段代码)
if(bol) {
// do something or do nothing...
}
// switch语句的基本写法(依据某个变量的值,从多个分支里挑选一个来执行)
switch(value) {
case value1:
// do something or do nothing...
break;
case value2:
// do something or do nothing...
break;
default:
// do something or do nothing...
}
Tips:
if-else语句可以有单选、双选、多选、嵌套等多种用法。
单选(基本写法,小括号内参数必须为boolean数据类型,且当"bol"为true时,大括号内代码才执行,
否则直接跳过本段代码)
if(bol) {
// do something or do nothing...
}
双选(小括号内参数必须为boolean数据类型,且当"bol"为true时,大括号内代码才执行,
否则必须执行"else"中的代码)
if(bol) {
// do something or do nothing...
} else {
// do something else or do nothing...
}
多选(小括号内参数必须为boolean数据类型,且当"bol1"为true时,大括号内代码才执行,
否则依次进行判断,一旦满足某一条件,立刻执行该段代码且后续不再进行判断)
if(bol) {
// do something or do nothing...
} else if(bol2) {
// do something else or do nothing...
} else if(bol3) {
// do something else or do nothing...
} else {
// do something else or do nothing...
}
嵌套(小括号内参数必须为boolean数据类型,且当"bol1"为true时,大括号内代码才执行,
然后当"bol2"为true时,继续执行...以此类推)
if(bol1) {
// do something or do nothing...
if(bol2) {
// do something else or do nothing...
}
}
switch语句的注意事项
default 为默认选项,当所有的 case 都不匹配时,执行 default 中的语句。
"注意":每次"case"结尾都要加"break",否则会出现 "case穿透",
程序就会无视后续case的条件,直接继续执行下一个case分支的代码,直到碰到终止语句或者switch结束
3. 循环结构
在Java中,循环结构的语句有:fori、while、do-while、增强for
// fori循环的基本写法(多用于明确循环次数时使用)
for (int i = 0; i < 5; i++) {
System.out.println("循环次数:" + i);
}
// while循环先对条件进行判断,条件为true时才会执行循环体,直到条件不满足才会停止
int i = 0;
while (i < 5) {
System.out.println("循环次数:" + i);
i++;
}
// do-while循环会先执行一次循环体,然后再判断条件,条件为true时才会执行循环体,直到条件不满足才会停止
int i = 0;
do {
System.out.println("循环次数:" + i);
i++;
} while (i < 5) {
// do something...
i++;
}
// 增强for循环主要用于遍历数组或者集合
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
System.out.println("本次循环的值为:" + num);
}
Tips:
while循环一定要注意死循环问题,当条件永远为true时,循环将无限次执行,导致内存溢出
4. break,continue
break:
杀死当前循环体,直接从当前循环体中跳出,意为"打断","结束";
continue:
跳过本次循环序列,是指从本次循环中跳出,意为"跳过","继续";
"注意:break和continue只会对当前循环体产生影响,并不会对外层循环产生影响!"
九、数组
1. 数组的基本概念
在Java中,数组的概念是 用于存储一组相同数据类型元素的容器,元素的顺序存在连续性。
数组本身属于引用数据类型,其存储的元素可以是基本数据类型,也可以是引用数据类型。
数组的索引是从0开始的,其表示的是数组元素距离首地址的偏移量。
当数组存储的元素也为数组时,我们称之为多维数组。
2. 数组的特点
在Java中,数组具有 固定长度、相同类型、连续存储、索引访问等特点。
固定长度:数组一旦创建,其长度就是固定且不可改变的
相同类型:数组中存储的元素,其数据类型必须是相同的
连续存储:数组中存储的元素,其存放顺序是有序的(按照声明时的前后顺序排列),这使得数组的访问效率较高
索引访问:通过索引(从0开始)可以直接访问数组中的任意元素,时间复杂度为O(1)
3. 数组的创建与操作
// ----------------------------------- 声明数组 -----------------------------------
// 声明int类型数组
int[] arr1;
int arr2[]; // 不推荐这种写法
// 声明引用类型数组
String[] strArr;
// ----------------------------------- 初始化数组 ---------------------------------
// 静态初始化:直接指定元素值
int[] nums = {1, 2, 3, 4, 5};
// 动态初始化:仅指定长度,元素为默认值
int[] scores = new int[5]; // 元素默认值为0
// 先声明后初始化
String[] names;
names = new String[]{"Alice", "Bob", "Charlie"};
// ----------------------------------- 数组的操作 ---------------------------------
// 1.通过索引访问元素
int[] arr = {10, 20, 30};
System.out.println(arr[0]); // 输出10(第一个元素)
arr[1] = 25; // 修改第二个元素
// 2.获取数组的长度
int length = arr.length; // 使用length属性,注意没有括号
// 3.遍历数组
// 3.1 fori循环
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 3.2 增强for循环
for (int num : arr) {
System.out.println(num);
}
4. 数组的内存示意图:
栈中存储变量,变量记录着数组实体的地址值
堆中存储数据实体,数据实体带有其唯一的地址值
栈中的变量,通过地址值连接找到对应堆中的数组实体数据。
5. 数组的常见算法
5.1 数值型数组的特征值计算(最大值、最小值、求和、平均值)
/**
* ----------------------------------- 查找数组中的最大值 -----------------------------------
*/
public static double findMax(double[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空或长度为0");
}
double max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
}
/**
* ----------------------------------- 查找数组中的最小值 -----------------------------------
*/
public static double findMin(double[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空或长度为0");
}
double min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] < min) {
min = array[i];
}
}
return min;
}
/**
* ----------------------------------- 计算数组元素的总和 -----------------------------------
*/
public static double calculateSum(double[] array) {
if (array == null || array.length == 0) {
return 0;
}
double sum = 0;
for (double num : array) {
sum += num;
}
return sum;
}
/**
* ----------------------------------- 计算数组元素的平均值 -----------------------------------
*/
public static double calculateAverage(double[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("数组不能为空或长度为0");
}
return calculateSum(array) / array.length;
}
5.2 数组元素的赋值(杨辉三角、回型数)
/**
* 生成杨辉三角
* 特点:
* 1. 第i行有i个元素
* 2. 每行的第一个和最后一个元素都是1
* 3. 中间元素等于上一行相邻两个元素之和
*/
public static int[][] generateYanghuiTriangle(int rows) {
if (rows <= 0) {
return new int[0][];
}
int[][] triangle = new int[rows][];
for (int i = 0; i < rows; i++) {
// 第i行有i+1个元素
triangle[i] = new int[i + 1];
// 第一个元素为1
triangle[i][0] = 1;
// 最后一个元素为1
triangle[i][i] = 1;
// 填充中间元素
for (int j = 1; j < i; j++) {
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
}
return triangle;
}
/**
* 生成回型数(螺旋矩阵)
* 特点:数字从1开始按顺时针螺旋方式填充n×n矩阵
*/
public static int[][] generateSpiralMatrix(int n) {
if (n <= 0) {
return new int[0][0];
}
int[][] matrix = new int[n][n];
int current = 1; // 要填充的当前数字
int top = 0; // 上边界
int bottom = n - 1; // 下边界
int left = 0; // 左边界
int right = n - 1; // 右边界
while (current <= n * n) {
// 从左到右填充上边界
for (int col = left; col <= right; col++) {
matrix[top][col] = current++;
}
top++; // 上边界下移
// 从上到下填充右边界
for (int row = top; row <= bottom; row++) {
matrix[row][right] = current++;
}
right--; // 右边界左移
// 从右到左填充下边界(如果还有行需要填充)
if (top <= bottom) {
for (int col = right; col >= left; col--) {
matrix[bottom][col] = current++;
}
bottom--; // 下边界上移
}
// 从下到上填充左边界(如果还有列需要填充)
if (left <= right) {
for (int row = bottom; row >= top; row--) {
matrix[row][left] = current++;
}
left++; // 左边界右移
}
}
return matrix;
}
5.3 数组的赋值、复制、反转、扩容、缩容
----------------------------------- 赋值数组 -----------------------------------
int[] oldArr = {1, 2, 3, 4, 5};
int[] newArr;
newArr = oldArr;
/**
* ----------------------------------- 复制数组 -----------------------------------
*/
public static int[] copyArray(int[] array) {
if (array == null) {
return null;
}
int[] newArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
/**
* 反转数组
*/
public static int[] reverseArray(int[] array) {
if (array == null || array.length <= 1) {
return array;
}
int[] reversed = new int[array.length];
for (int i = 0; i < array.length; i++) {
reversed[i] = array[array.length - 1 - i];
}
return reversed;
}
/**
* ----------------------------------- 数组扩容 -----------------------------------
* @param array 原数组
* @param newLength 新长度,必须大于原长度
*/
public static int[] expandArray(int[] array, int newLength) {
if (array == null) {
return null;
}
if (newLength <= array.length) {
throw new IllegalArgumentException("新长度必须大于原长度");
}
int[] newArray = new int[newLength];
for (int i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
/**
* ----------------------------------- 数组缩容 -----------------------------------
* @param array 原数组
* @param newLength 新长度,必须小于原长度
*/
public static int[] shrinkArray(int[] array, int newLength) {
if (array == null) {
return null;
}
if (newLength >= array.length || newLength <= 0) {
throw new IllegalArgumentException("新长度必须大于0且小于原长度");
}
int[] newArray = new int[newLength];
for (int i = 0; i < newLength; i++) {
newArray[i] = array[i];
}
return newArray;
}
5.4 数组元素的查找(线性查找、二分法查找(有序数组))
/*
* ----------------------------------- 线性查找 -----------------------------------
*/
public static int linearSearch(int[] array, int target) {
if (array == null || array.length == 0) {
return -1;
}
// 遍历数组,逐个比较
for (int i = 0; i < array.length; i++) {
if (array[i] == target) {
return i; // 找到目标,返回索引
}
}
return -1; // 未找到
}
/*
* ----------------------------------- 二分法查找 -----------------------------------
* 二分法查找依赖于《有序数组》(通常为升序),通过不断缩小查找范围实现高效查找
* 1. 取数组中间元素与目标值比较;
* 2. 若中间元素等于目标值,返回索引;
* 3. 若中间元素大于目标值,缩小范围到左半部分;
* 4. 若中间元素小于目标值,缩小范围到右半部分;
* 5. 重复上述步骤,直到找到目标或范围为空(为空则代表未找到)
*/
// 非递归实现二分查找
public static int binarySearch(int[] array, int target) {
if (array == null || array.length == 0) {
return -1;
}
int left = 0; // 左边界索引
int right = array.length - 1; // 右边界索引
while (left <= right) {
int mid = left + (right - left) / 2; // 计算中间索引(避免溢出)
if (array[mid] == target) {
return mid; // 找到目标
} else if (array[mid] > target) {
right = mid - 1; // 目标在左半部分
} else {
left = mid + 1; // 目标在右半部分
}
}
return -1; // 未找到
}
// 递归实现二分查找
public static int binarySearchRecursive(int[] array, int target, int left, int right) {
if (left > right) {
return -1; // 范围无效,未找到
}
int mid = left + (right - left) / 2;
if (array[mid] == target) {
return mid;
} else if (array[mid] > target) {
return binarySearchRecursive(array, target, left, mid - 1); // 递归左半部分
} else {
return binarySearchRecursive(array, target, mid + 1, right); // 递归右半部分
}
}
两种查找算法对比
| 特性 | 线性查找 | 二分法查找 |
|---|---|---|
| 适用数组 | 无序或有序 | 必须有序(升序) |
| 时间复杂度 | O(n) | O(log n) |
| 空间复杂度 | O (1)(非递归) | O (1)(非递归)/ O (log n)(递归,栈空间) |
| 实现复杂度 | 简单 | 较复杂 |
| 适用场景 | 小规模数据、无序数组 | 大规模数据、有序数组 |
5.5 数组的排序(冒泡排序(最简单)、快速排序(最常用))
5.5.1 冒泡排序
核心思想:通过重复比较相邻元素并交换位置,使较大的元素逐渐 "浮" 到数组末尾
/*
* 在乱序数组中,从"首位"开始,与下一位进行比较,
* 将较大值前置或后置(也可以说"调换位置",取决于排序规则:升序较大值后置,降序较大值前置)
* 如此反复若干次(数组长度-1)即可成功排序
*/
public static int[] bubbleSort(int[] arr) {
//临时容器变量(空杯子)
int temp = 0;
//外层循环 决定了外部需要大循环几次(次数为 数组长度-1)
// 例:[1,2,3,4,5]
// 第一次:12,23,34,45
// 第二次:12,23,34
// 第三次:12,23
// 第四次:12
for (int i = 0; i < arr.length - 1; i++) {
//内部循环 进行比较(将较大值向后移)
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
5.5.2 快速排序
核心思想:采用分治法,选择一个基准元素,将数组分成两部分,然后递归排序
/**
* 快速排序实现
* 原理:选择一个"基准"元素,将数组分为两部分,
* 一部分所有元素都小于基准,另一部分所有元素都大于基准,
* 然后递归地对这两部分进行排序
*/
public static void quickSort(int[] array, int low, int high) {
if (array == null || array.length <= 1 || low >= high) {
return;
}
// 选择基准元素(这里选择数组中间的元素)
int pivot = array[low + (high - low) / 2];
// 将数组分为两部分
int i = low, j = high;
while (i <= j) {
// 从左向右找到第一个大于等于基准的元素
while (array[i] < pivot) {
i++;
}
// 从右向左找到第一个小于等于基准的元素
while (array[j] > pivot) {
j--;
}
// 如果找到的左侧元素位置小于等于右侧元素位置,则交换它们
if (i <= j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
i++;
j--;
}
}
// 递归排序左半部分
if (low < j) {
quickSort(array, low, j);
}
// 递归排序右半部分
if (high > i) {
quickSort(array, i, high);
}
}
6. Arrays工具类
在 Java 中,java.util.Arrays 是一个工具类,提供了一系列静态方法,用于操作数组(如排序、查找、填充、比较等)。它可以简化数组操作的代码,提高开发效率。
/*
* -------------------------------- 常见Arrays内置算法举例 --------------------------------
*/
// Java的Arrays工具类内置的二分法查找算法
int index = Arrays.binarySearch(sortedArr, target); // 返回查找到的索引,若返回负数则代表未找到
// Java的Arrays工具类内置的快速排序算法(改进的快速排序算法:双轴快速排序)
Arrays.sort(arr);