Java基础:基本语法(一)
文章目录
- Java基础:基本语法(一)
- 1. 前言
- 2. 开发环境搭建
- 2.1 Java开发工具包下载
- 2.2 环境变量配置
- 2.3 Java程序的运行过程
- 3. 数据类型
- 3.1 基本数据类型
- 3.2 引用数据类型
- 4. 常量与变量
- 4.1 常量
- 4.1.1 常量的概念
- 4.1.2 常量的使用
- 4.2 变量
- 4.2.1 变量的概念
- 4.2.2 变量的使用
- 5. 运算符
- 5.1 算术运算符
- 5.2 关系运算符(比较运算符)
- 5.3 位运算符
- 5.4 逻辑运算符
- 5.5 赋值运算符
- 5.6 条件运算符(三元运算符)
- 5.7 类型转换运算符
- 5.8 其他运算符
- 6. 流量控制
- 6.1 顺序结构
- 6.2 分支结构(选择结构)
- 6.2.1 if语句
- 6.2.2 if-else语句
- 6.2.3 if-else if-else语句
- 6.2.4 嵌套使用
- 6.2.5 switch语句
- 6.3 循环结构
- 6.3.1 while循环
- 6.3.2 do-while循环
- 6.3.3 for循环
- 6.3.4 循环嵌套
- 6.3.5 continue与break
- 7. 数组
- 7.1 数组的概念
- 7.2 数组的声明
- 7.3 数组的创建
- 7.3.1 静态创建数组
- 7.3.2 动态创建数组
- 7.4 数组的打印
- 7.5 数组的排序
- 7.5.1 选择排序
- 7.5.2 冒泡排序
- 7.5.3 快速排序
- 7.6 Arrays工具
- 7.6.1 排序方法
- 7.6.2 toString方法
- 7.6.3 fill方法
- 7.6.4 binarySearch二分查找
- 7.6.5 copyOf和copyOfRange方法
- 7.6.6 equals和compare方法
- 7.6.7 数组的数组(二维数组)
- 8. 方法
- 8.1 方法的好处
- 8.2 方法的定义
- 8.3 方法的调用
- 8.4 方法返回值
- 8.5 方法调用和内存结构
- 8.7 方法参数传递
- 8.8 方法的重载
- 8.9 可变参数(不定长参数)
- 8.10 return的用法
- 9. 命名规范
1. 前言
- Java SE是Java平台标准版(
Java Platform, Standard Edition
)的简称,用于开发和部署桌面、服务器以及嵌入设备和实时环境中的Java应用程序。- Java SE包括用于开发Java Web服务的类库,同时,Java SE为Java EE(
Java Platform, Enterprise Edition
)企业版和Java ME(Java Platform, Micro Edition
)微型版提供了基础。- Java SE就是基于JDK(
Java开发工具包,Java Development Kit
)和JRE(Java运行环境,Java Runtime Environment
),包含支持Java Web服务开发的类,并为Java 企业级开发提供基础。
2. 开发环境搭建
2.1 Java开发工具包下载
JDK官网下载
2.2 环境变量配置
- 新建一个环境变量
JAVA_HOME
。
变量名:JAVA_HOME
变量值:D:\Program Files\Java\jdk-11
- 修改
Path
,加入JAVA_HOME
下的bin
目录。
变量名:Path
变量值:%JAVA_HOME%\bin
win+R
打开cmd(命令提示符,Command Prompt
),查看安装的JDK版本。
java -version
2.3 Java程序的运行过程
Java程序的运行过程通常包括编译和执行两个主要阶段。
- 编译阶段:
javac
是Java编译器的命令行工具,它用于将Java源代码文件(.java
)编译成Java字节码文件(.class
)。
javac HelloWorld.java // 编译源代码
- 执行阶段:JVM加载字节码文件,解释执行或者将其编译成机器码,并执行。这个阶段需要JVM处于运行状态。
java HelloWorld // 运行程序
📌注:
- 设置Java文件编码格式为
ANSI
,解决中文乱码问题。- Java虚拟机(JVM)是运行Java字节码的虚拟机。JVM的运行流程可以分为几个阶段:加载(
Loading
)、验证(Verification
)、准备(Preparation
)、解析(Resolution
)、初始化(Initialization
)、使用(Using
)和卸载(Unloading
)。- JVM运行时,主要通过类加载器(
Class Loader
)、执行引擎(Execution Engine
)、运行时数据区(Runtime Data Area
)以及本地库接口(Native Interface
)协同工作来完成任务。
3. 数据类型
Java中的数据类型总体可以分为两大类,分别是基本数据类型和引用数据类型。
3.1 基本数据类型
Java基本类型共有八种,基本类型可以分为三类,字符类型char
,布尔类型boolean
以及数值类型byte
、short
、int
、long
、float
、double
。数值类型又可以分为整数类型byte
、short
、int
、long
和浮点数类型float
、double
。
Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。Java中还存在另外一种基本类型void
,它也有对应的包装类 java.lang.Void
,用于声明方法不返回任何值。八种基本类型表示范围如下:
大类 | 类型 | 说明 | 长度 | 最小值 | 最大值 | 备注 |
---|---|---|---|---|---|---|
整数类型 | byte | 字节型 | 8位(1字节) | -128(-2^7) | 127(2^7-1) | 最大数据存储量是255 |
short | 短整型 | 16位(2字节) | -32768(-2^15) | 32767(2^15-1) | 最大数据存储量是65536 | |
int | 整型 | 32位(4字节) | -2,147,483,648(-2^31) | 2,147,483,647(2^31-1) | 最大数据存储量是2^32-1 | |
long | 长整型 | 64位(8字节) | -9,223,372,036,854,775,808(-2^63) | 9,223,372,036,854,775,807(2^63 -1) | 最大数据存储量是2^64-1,通常以L 或l 作为后缀来表示长整型字面量,如123L | |
浮点数类型 | float | 单精度 | 32位(4字节) | 1.4e-45 | 3.4028235e38 | 通常以F 或f 作为后缀来表示浮点数字面量,如3.14F |
double | 双精度 | 64位(8字节) | 4.9e-324 | 1.7976931348623157e308 | 双精度是默认的浮点数类型,所以通常不需要添加后缀,但如果需要明确指定,可以使用D 或d 作为后缀,如3.14D | |
布尔类型 | boolean | 布尔型 | 8位(1字节) | false | true | 表示逻辑值,只有true 和false 两种取值 |
字符类型 | char | 字符型 | 16位(2字节) | '\u0000' (即0) | '\uffff' (即65,535) | 存储Unicode 码,字符字面量通常用单引号括起来,如'a' |
📌注:
- 自动类型转换(隐式转换):小的数据类型向大的数据类型转换(
byte—>short—>int—>long
,char—>int
),或者整数类型转换成浮点数类型,都可以实现自动转换的。- 强制类型转换(显式转换):大的数据类型向小的数据类型转换,或者浮点数类型转换成整数类型,需要使用强制转换。要在转换类型加括号,如果是浮点数转整数,直接去掉小数点,只保留整数(不考虑四舍五入情况)。
3.2 引用数据类型
引用数据类型是Java中的非基本数据类型,它们在内存中通过引用来访问实际的对象。引用数据类型包括类(Class
)、接口(Interface
)、数组(Array
)、枚举(Enum
)和注解(Annotation
)。
- 类:是一种面向对象编程的基本概念,它是具有相同属性和行为的实体的集合或模板。类定义了对象的数据结构,包括对象的属性(变量)和方法(行为)。
- 接口:接口定义了一个类必须实现的方法,但接口本身不提供这些方法的实现,一个类可以实现多个接口。
- 数组:数组是一种引用数据类型,用于存储多个相同类型的元素。
- 枚举:枚举是一种特殊的引用数据类型,用于表示一组命名的常量。
- 注解:注解是一种用于为程序提供元数据信息的引用数据类型。
对象引用的强度或可达性级别被分为四种,它们由高到低依次为:强引用(Strong Reference
)、软引用(Soft Reference
)、弱引用(Weak Reference
)和虚引用(Phantom Reference
)。
- 强引用:这是最常见的引用类型。当创建一个对象并将其赋值给一个变量时,就创建了一个强引用。只要强引用存在,垃圾回收器(
GC
)就永远不会回收掉被引用的对象。 - 软引用:需要通过
SoftReference
类来实现,对于只有软引用的对象来说,当系统内存足够时,它不会被回收,当系统内存空间不足时,它会被回收。软引用通常用于对内存敏感的程序中,如缓存数据。 - 弱引用:弱引用需要用
WeakReference
类来实现,它比软引用生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM内存空间是否足够,总会回收该对象占用的内存。 - 虚引用:虚引用需要
PhantomReference
类来实现,它不能单独使用,必须和引用队列(ReferenceQueue
)联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态,无法通过虚引用获取对象实例(get
方法总是返回null
),存在的唯一目的就是能在这个对象被回收时收到一个系统通知。
级别 | 例子 | 回收时机 | 用途 | 生存时间 |
---|---|---|---|---|
强引用 | Object obj = new Object(); | 从来不会 | 强引用用于表示程序正在使用的对象,如局部变量、实例变量等 | JVM停止运行时终止 |
软引用 | SoftReference a = new SoftReference(new A()); | 在内存不足时 | 联合ReferenceQueue 构造有效期短/占内存大/生命周期长的对象的二级高速缓冲器(内存不足时才清空) | 内存不足时终止 |
弱引用 | WeakReference a = new WeakReference(new A()); | 在垃圾回收时 | 联合ReferenceQueue 构造有效期短/占内存大/生命周期长的对象的二级高速缓冲器(系统发生GC则清空) | GC运行后终止 |
虚引用 | PhantomReference a = new PhantomReference(new A(), referenceQueue); | 在垃圾回收时 | 联合ReferenceQueue 来跟踪对象被垃圾回收器回收的活动 | GC运行后终止 |
4. 常量与变量
4.1 常量
4.1.1 常量的概念
- 不可变性:常量的值在初始化后不能被修改。
- 命名规范:常量名通常使用大写字母,单词之间使用下划线
_
分隔,以区别变量名,例如PI
或MAX_VALUE
。 - 声明方式:使用
final
关键字来声明一个常量,并且如果它是静态的(即属于类而不是实例),则还会使用static
关键字。
4.1.2 常量的使用
- 使用
final
关键字声明常量,并在声明时初始化其值。 - 对于静态常量,可以通过类名直接访问;对于非静态常量,需要通过对象实例来访问;但由于常量通常设计为不可变的,并且与具体的对象实例无关,因此大多数常量都是静态的。
- 常量可以是任何数据类型,包括基本数据类型常量、引用类型常量、枚举常量、自定义常量。
- 在Java接口中,所有字段(即常量)都隐式地是
static
和final
的,因此不需要显式声明这两个关键字。 - 枚举类型
enum
在Java中经常用做常量,因为它们提供了一种类型安全的方式来表示一组固定的常量值。
4.2 变量
4.2.1 变量的概念
变量是程序里最基本的存储单元,它由数据类型、变量名、变量值组成。从本质上来说,变量其实就是内存里的一小块存储区域,该区域只能存储声明变量时指定类型的数据。我们创建使用每个变量前,必须先声明,在内存中申请空间,然后对其进行初始化赋值,接着就可以通过变量名来访问和使用这块内存区域了。语法格式如下:
// 先声明,再赋值
数据类型 变量名;
变量名 = 变量值;
// 声明的同时并赋值
数据类型 变量名 = 变量值;
// 同时定义多个类型相同的变量
数据类型 变量名1 = 变量值1,变量名2 = 变量值2,变量名3……;
4.2.2 变量的使用
在使用变量的过程中,还有一些事项需要我们注意:
- 变量必须要先声明,才能使用;
- 声明变量时,要指定变量的类型(基本数据类型、引用数据类型);
- 变量的作用域:其定义所在的一对
{}
内; - 变量只有在其作用域内才有效;
- 同一个作用域(代码块)中,变量名不能相同;
- 变量在声明时可以没有值,使用时必须要有值的;
- JDK 10可以使用
var
作为局部变量类型推断标识符(Local-Variable Type Inference),仅适用于局部变量,使用var
声明时,必须要赋初始值。
5. 运算符
Java运算符主要分为以下几类:算术运算符、关系运算符(比较运算符)、位运算符、逻辑运算符、赋值运算符、条件运算符(三元运算符)、类型转换运算符等。
5.1 算术运算符
+
:加法(当操作数是字符串时,用作连接符)-
:减法*
:乘法/
:除法%
:取余(模运算)++
:自增(自身加1)--
:自减(自身减1)
5.2 关系运算符(比较运算符)
所有计算的结果,会返回boolean
类型的值。
==
:等于!=
:不等于>
:大于<
:小于>=
:大于等于<=
:小于等于instanceof
:判断其左边对象是否为其右边类的实例
5.3 位运算符
<<
:左移(右边用0填充,即低位补0)>>
:右移(有符号,左边用符号位填充,即高位补符号位)>>>
:无符号右移(无符号,左边用0填充,即高位补0)&
:按位与(两边同是1,才为1,不然为0)|
:按位或(两边有一边为1,则结果为1,否则为0)^
:按位异或(两边相同,则结果为0,两边不同,则结果为1)~
:按位非(取反,如果是0,则取值为1,如果是1,则取值为0)
5.4 逻辑运算符
&&
:逻辑与(短路与)||
:逻辑或(短路或)!
:逻辑非
5.5 赋值运算符
=
:赋值+=
:加法赋值-=
:减法赋值*=
:乘法赋值/=
:除法赋值%=
:取余赋值<<=
:左移赋值>>=
:右移赋值>>>=
:无符号右移赋值&=
:按位与赋值|=
:按位或赋值^=
:按位异或赋值
5.6 条件运算符(三元运算符)
?:
:该运算符有3个操作符,并且需要判断布尔表达式的值,主要是决定哪个值应该赋值给变量。例如:var c = (a > b) ? a : b;
。
5.7 类型转换运算符
(type)
:强制类型转换,用于显式地将一种类型的值转换为另一种类型。例如:int a = (int) 3.14;
。
5.8 其他运算符
.
:成员运算符,用于访问对象的成员(属性和方法)。[]
:索引运算符,用于访问数组或集合的元素。()
:方法调用运算符,用于调用方法。new
:实例化运算符,用于创建对象的实例。instanceof
:用于测试对象是否是一个类的实例。->
:Lambda运算符(箭头运算符),是Java 8引入的的一个新特性,它分隔了参数列表和Lambda体,用于定义Lambda表达式。::
:方法引用运算符,是Java 8引入的一个新特性,用于引用现有方法或构造函数,通常与Lambda表达式一起使用。
6. 流量控制
在Java中,流程控制主要包括顺序、分支(选择)、循环三大结构。
6.1 顺序结构
代码自上而下顺序执行。
6.2 分支结构(选择结构)
使用
if-else
语句和switch
语句实现条件分支。
6.2.1 if语句
语法格式如下:
if (condition) {
// 条件成立true,执行这里的代码
// 如果只有一行或一组代码,大括号可以省略
}
6.2.2 if-else语句
语法格式如下:
if (condition) {
// 条件成立true,执行这里的代码
} else {
// 必须和对应的if一起出现,不能单独出现
// 条件不成立false,执行这里的代码
}
6.2.3 if-else if-else语句
语法格式如下:
if (condition1) {
// 如果condition1为true,执行这里的代码
} else if (condition2) {
// 如果condition1为false但condition2为true,执行这里的代码
} else if (condition3) {
// 如果前两个条件都为false但condition3为true,执行这里的代码
// 可以继续添加更多的else if子句
} else {
// 如果所有条件都为false,执行这里的代码
}
6.2.4 嵌套使用
if-else
语句可以嵌套使用,即一个if
或else
语句块内部可以包含另一个完整的if-else
语句。这样可以根据多个条件进行更复杂的判断。
6.2.5 switch语句
语法格式如下:
switch (variable) {
case value1:
// 当variable的值等于value1时执行
break;
case value2:
// 当variable的值等于value2时执行
break;
// 可以添加更多的case
default:
// 当variable的值不匹配任何case时执行
break;
}
6.3 循环结构
使用
while
、do-while
和for
循环。
6.3.1 while循环
当型循环,语法格式如下:
while (condition) {
// 当条件成立,反复执行这段代码(循环体)
}
6.3.2 do-while循环
直到型循环,先执行一次循环体,后面根据while
条件是否为真,如果为真继续执行。语法格式如下:
do {
// 循环体
} while (condition); // 后面有分号
6.3.3 for循环
语法格式如下:
for (表达式1; 表达式2; 表达式3) {
// 循环体
}
for
循环执行的过程:
- 执行表达式1,赋初始值;
- 执行表达式2,条件判定(
boolean
类型值),如果条件不成立,退出循环; - 如果表达式2条件成立,执行循环体;
- 执行表达式3,让循环条件变动;
- 从第2步开始循环。
for
循环的特殊用法:
- 增强的
for
循环(for-each
循环),用于遍历数组或集合中的元素,而不需要通过索引来访问每个元素。
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.println(number);
}
List<String> list = Arrays.asList("a", "b", "c");
for (String item : list) {
System.out.println(item);
}
- 无限循环,通常用于需要一直运行直到某个条件被外部触发的场景(如服务器监听)。
for (;;) {
// 循环体内的代码将无限执行,直到遇到break语句或程序被终止
}
- 可以在
for
循环的初始化部分声明多个变量。
for (int i = 0, j = 10; i < j; i++, j--) { // 如果控制两种不同类型的变量,需要在外面做声明处理
System.out.println("i: " + i + ", j: " + j);
}
- 在
for
循环中声明并初始化数组。
int[] numbers = new int[5];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i * 2;
}
- 在
for
循环中嵌套switch
语句。
for (int i = 0; i < 5; i++) {
switch (i) {
case 0:
System.out.println("Zero");
break;
case 1:
System.out.println("One");
break;
// ... 其他case
default:
System.out.println("Default");
}
}
- 在
for
循环中使用Lambda表达式。
List<String> list = Arrays.asList("a", "b", "c", "a", "b");
list.stream()
.filter(item -> !item.equals("a")) // 过滤掉所有"a"
.forEach(System.out::println); // 打印剩下的元素
- 使用
for
循环进行字符串迭代。
String str = "Hello";
for (char c : str.toCharArray()) { // toCharArray()方法将字符串转换为字符数组
System.out.print(c + " ");
}
快速生成for
循环的处理方式:
fori
从0到指定值循环。100.fori
从0-100循环。100.forr
从100-0循环。
📌注:
for
循环变量的初值,最好在for
循环中声明,因为它有助于减少变量的作用域并提高代码的可读性。- 迪米特法则(Law of Demeter,LOD),也称为最少知识原则(Least Knowledge Principle,LKP),是一种面向对象编程(OOP)和面向对象设计(OOD)的原则。通过降低类之间的耦合度、提高内聚性和简化依赖关系,可以创建出更加可维护、可扩展和可复用的软件系统。
6.3.4 循环嵌套
我国传统数学名著《九章算术》记载:今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?使用循环解决计算鸡兔同笼问题。代码示例如下:
public class ChiRabCage {
public static void main(String[] args) {
for (int i = 0; i <= 35; i++) { // 鸡的数量
for (int j = 1; j <= 35 - i; j++) { // 兔的数量
if (i * 2 + j * 4 == 94 && i + j == 35) {
System.out.println("鸡的数量:" + i + ",兔的数量:" + j);
}
}
}
}
}
🆚🆚运行结果:
鸡的数量:23,兔的数量:12
6.3.5 continue与break
- break:退出当前循环,
break
后面的语句都不执行。 - continue:结束本次循环,
continue
后的语句本次不再执行,从循环的下一次开始执行。
7. 数组
7.1 数组的概念
数组(Array)是一种引用数据类型,用于存储相同类型数据的有序集合。数组中的每个元素都有一个唯一的索引,该索引用于访问和修改数组中的元素。数组在内存中占用一段连续的存储空间,因此通过索引访问数组元素的速度非常快。
7.2 数组的声明
数组的声明用于定义数组的类型和名称,但并不会为数组分配内存空间。数组声明的基本语法如下:
int[] array; // 推荐使用
int array[];
7.3 数组的创建
7.3.1 静态创建数组
在声明数组的同时直接指定数组的元素值,也称为字面量初始化。
int[] intArray = {1, 2, 3, 4, 5}; // 静态初始化整数数组
double[] doubleArray = {1.2, 3.4, 5.6, 7.8}; // 静态初始化双精度浮点数数组
String[] stringArray = {"apple", "banana", "cherry"}; // 静态初始化字符串数组
7.3.2 动态创建数组
先声明数组变量,然后为其分配内存空间并指定长度,但不立即为数组元素赋值。
// 数组类型[] 数组名 = new 数组类型[长度(数组中存储元素的个数)]
int[] x = new int[3]; // 动态初始化,指定长度为3
x[0] = 1; // 为数组的元素赋值
x[1] = 2;
x[2] = 3;
System.out.println(x[0]);
System.out.println(x[1]);
System.out.println(x[2]);
📌注:
- 数组一旦创建,其长度就不能改变。
- 在声明数组变量后,必须对其进行初始化(即分配内存空间)才能使用。
- 数组在内存中占用连续的空间,可以通过索引来访问数组中的元素。
- 访问数组元素时,索引从0开始,到数组长度减1结束。如果越界访问数组元素,将会抛出
ArrayIndexOutOfBoundsException
异常。
7.4 数组的打印
可以使用循环来遍历数组并逐个打印元素。代码示例如下:
public class TestPrint {
public static void main(String[] args) {
int[] a = {1, 3, 5, 7, 9};
// 使用增强的for循环(for-each循环)
for (int i : a) { // a.for
System.out.print(i + "\t");
}
System.out.println();
// 使用基本for循环
for (int i = 0; i < a.length; i++) { // 正序
int x = a[i]; // a.fori
System.out.print(x + "\t");
}
System.out.println();
for (int i = a.length - 1; i >= 0; i--) { // 倒序
int x = a[i]; // a.forr
System.out.print(x + "\t");
}
}
}
🆚🆚运行结果:
1 3 5 7 9
1 3 5 7 9
9 7 5 3 1
📌注:
- 使用
Arrays
类的toString
方法(适用于所有类型的数组);- 对于更复杂的数组处理或函数式编程风格,可以使用Java 8的Stream API(
java.util.stream
);- 对于大型数组或需要频繁拼接字符串的情况,使用
StringBuilder
可以提高性能。
7.5 数组的排序
7.5.1 选择排序
选择排序(Selection Sort)是一种简单直观的排序算法,其基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。选择排序的主要步骤如下:
- 初始化:首先,在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置。
- 继续选择:然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。
- 重复执行:重复步骤2,直到所有元素均排序完毕。
选择排序的特点:时间复杂度O(n^2)
,空间复杂度O(1)
,原地排序算法,不需要额外的存储空间,不稳定排序。代码示例如下:
/* 选择排序 */
public class SelectionSort {
public static void main(String[] args) {
int[] x = {56, 12, 78, 90, 34};
for (int i = 0; i < x.length - 1; i++) { // 控制外层循环
int minIndex = i; // 假设当前位置是最小的
for (int j = i + 1; j < x.length; j++) { // 在剩余未排序的元素中找最小元素
if (x[j] < x[minIndex]) { // 如果找到更小的元素,更新最小元素的索引
minIndex = j;
}
}
if (minIndex != i) { // 将找到的最小元素与第一个未排序的元素交换位置
int tmp = x[i];
x[i] = x[minIndex];
x[minIndex] = tmp;
}
System.out.println("第" + (i + 1) + "轮:");
for (int a : x) {
System.out.print(a + "\t");
}
System.out.println();
}
}
}
🆚🆚运行结果:
第1轮:
12 56 78 90 34
第2轮:
12 34 78 90 56
第3轮:
12 34 56 90 78
第4轮:
12 34 56 78 90
7.5.2 冒泡排序
冒泡排序(Bubble Sort)是一种交换排序的算法,其基本思想是两两比较,进行交换位置,得到一个有序的序列。冒泡排序的主要步骤如下:
- 比较相邻元素:从列表的第一个元素开始,比较每对相邻的项,如果它们的顺序错误就交换它们。
- 遍历数组:在列表的剩余部分(即未排序的部分)中,重复步骤1。
- 重复过程:在每次遍历后,数组中最大的元素就像气泡一样“浮”到数组的末尾。然后减少遍历的范围(因为最后一个元素已经是最大的,不需要再比较),并重复步骤1和2,直到没有元素需要交换为止。
冒泡排序的特点:时间复杂度O(n^2)
,空间复杂度O(1)
,原地排序算法,不需要额外的存储空间,稳定排序。代码示例如下:
/* 冒泡排序 */
public class BubbleSort {
public static void main(String[] args) {
int[] x = {56, 12, 78, 90, 34};
for (int i = 0; i < x.length - 1; i++) { // 外循环
for (int j = 0; j < x.length - i - 1; j++) { // 内循环
if (x[j] > x[j + 1]) { // 交换值
int t = x[j];
x[j] = x[j + 1];
x[j + 1] = t;
}
}
System.out.println("第" + (i + 1) + "轮:");
for (int a : x) {
System.out.print(a + "\t");
}
System.out.println();
}
}
}
🆚🆚运行结果:
第1轮:
12 56 78 34 90
第2轮:
12 56 34 78 90
第3轮:
12 34 56 78 90
第4轮:
12 34 56 78 90
7.5.3 快速排序
快速排序(Quick Sort)是一种非常高效的排序算法,它采用了分治(Divide and Conquer)的策略。快速排序的主要步骤如下:
- 选择基准(Pivot):从待排序序列中选取一个元素作为基准,通常选择序列的第一个或最后一个元素。
- 分区操作(Partition):将序列分成两个子序列,左边子序列的元素都小于或等于基准,右边子序列的元素都大于基准。
- 递归排序(Recursion):递归地对基准元素左右两边的子序列进行快速排序。
快速排序的特点:平均时间复杂度O(n log n)
,最坏时间复杂度O(n^2)
,平均空间复杂度O(log n)
,最坏空间复杂度O(n)
,原地排序算法,除了递归栈外不需要额外的存储空间,不稳定排序。代码示例如下:
public class QuickSort1 {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 获取分区后的枢纽位置
int pivotIndex = partition(arr, low, high);
// 分别对枢纽左右两边的子数组进行递归排序
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
// 选择数组的最后一个元素作为枢纽值
int pivot = arr[high];
int i = (low - 1);
// 遍历数组,将小于枢纽值的元素放到左边,大于枢纽值的元素放到右边
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
swap(arr, i + 1, high);
return i + 1;
}
// 将基准元素交换到其最终位置
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 返回枢纽位置
public static void main(String[] args) {
int[] arr = {56, 12, 78, 90, 34};
quickSort(arr, 0, arr.length - 1);
// 输出排序后的数组
System.out.println(java.util.Arrays.toString(arr));
}
}
public class QuickSort2 {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 获取分区后基准元素的位置
int pivotIndex = partition(arr, low, high);
// 分别对基准元素左右两边的子数组进行快速排序
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
// 选择数组的第一个元素作为基准元素
int pivot = arr[low];
int i = low + 1;
int j = high;
// 进行迭代,直到i和j指向同一个位置
while (i <= j) {
// 从右向左找到第一个小于等于基准元素的元素
while (i <= j && arr[j] > pivot) {
j--;
}
// 从左向右找到第一个大于等于基准元素的元素
while (i <= j && arr[i] <= pivot) {
i++;
}
// 交换i和j指向的元素
if (i < j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准元素放到正确的位置
arr[low] = arr[j];
arr[j] = pivot;
// 返回基准元素的位置
return j;
}
public static void main(String[] args) {
int[] arr = {56, 12, 78, 90, 34};
quickSort(arr, 0, arr.length - 1);
System.out.println(java.util.Arrays.toString(arr));
}
}
🆚🆚运行结果:
[12, 34, 56, 78, 90]
📌注:
效率:快速排序>选择排序>冒泡排序(冒泡排序在内循环交换,选择排序在外循环交换)
7.6 Arrays工具
7.6.1 排序方法
Arrays.sort()
是Java中的数组排序,它是对一个数组的所有元素进行排序,并且是按从小到大的顺序。代码示例如下:
public class QuickSort3 { // 快速排序
public static void main(String[] args) {
int[] arr = {56, 12, 78, 90, 34};
long t1 = System.currentTimeMillis(); // 返回当前时间的毫秒数
Arrays.sort(arr); // 数组进行排序
long t2 = System.currentTimeMillis(); // 返回当前时间的毫秒数
System.out.println(Arrays.toString(arr));
long t = t2 - t1;
System.out.println("执行时间:" + t + "毫秒");
}
}
🆚🆚运行结果:
[12, 34, 56, 78, 90]
执行时间:0毫秒
7.6.2 toString方法
Arrays.toString()
用于将数组转换为字符串,可以快速的输出数组的内容。
7.6.3 fill方法
Arrays.fill()
对数组元素进行填充。代码示例如下:
public class ArrayTest {
public static void main(String[] args) {
int x[] = new int[5];
x[0] = 29;
x[3] = 89;
//起始位包含,结束位置不包含,对数组进行填充
Arrays.fill(x, 1, 3, 100);
System.out.println(Arrays.toString(x));
}
}
🆚🆚运行结果:
[29, 100, 100, 89, 0]
7.6.4 binarySearch二分查找
数组必须经过排序再进行二分查找,返回数组中元素所在的下标位置,如果找不到,返回负值。
public class ArraysBinarySearch {
public static void main(String[] args) {
int[] y = {12, 56, 34, 88, 66};
Arrays.sort(y);
System.out.println(Arrays.toString(y));
int n = Arrays.binarySearch(y, 66);
System.out.println(n);
}
}
🆚🆚运行结果:
[12, 34, 56, 66, 88]
3
7.6.5 copyOf和copyOfRange方法
public class ArraysTest {
public static void main(String[] args) {
int[] x = {66, 33, 44, 55, 11, 22};
System.out.println(Arrays.toString(x));
int[] y = Arrays.copyOf(x, 4);
System.out.println(Arrays.toString(y));
int[] z = Arrays.copyOfRange(x, 1, 3);
System.out.println(Arrays.toString(z));
}
}
🆚🆚运行结果:
[66, 33, 44, 55, 11, 22]
[66, 33, 44, 55]
[33, 44]
7.6.6 equals和compare方法
public class ArraysTest2 {
public static void main(String[] args) {
int[] x = {66, 33, 44, 55, 11, 22};
int[] y = Arrays.copyOf(x, 4);
System.out.println("x:" + Arrays.toString(x));
System.out.println("y:" + Arrays.toString(y));
int[] m = new int[5];
// 参数1:源数组 参数2:从源数组哪个下标开始复制
// 参数3:复制到的目标数组 参数4:从目标数组的哪个下标位置开始放入
// 参数5:复制源数组多长
System.arraycopy(x, 1, m, 0, 5);
System.out.println("m:" + Arrays.toString(m));
boolean b = Arrays.equals(x, y);
System.out.println("两数组的值是否相同(相同为true,不同为false):" + b);
int i = Arrays.compare(x, y);
System.out.println("两数组按字典顺序比较(相同为0,前者大为正,后者大为负):" + i);
}
}
🆚🆚运行结果:
x:[66, 33, 44, 55, 11, 22]
y:[66, 33, 44, 55]
m:[33, 44, 55, 11, 22]
两数组的值是否相同(相同为true,不同为false):false
两数组按字典顺序比较(相同为0,前者大为正,后者大为负):2
7.6.7 数组的数组(二维数组)
在Java中,二维数组被看作数组的数组,即二维数组为一个特殊的一维数组,其每个元素又是一个一维数组。Java并不直接支持二维数组,但是允许定义数组元素是一维数组的一维数组,以达到同样的效果。代码示例如下:
public class ArraysTest3 {
public static void main(String[] args) {
// 静态创建二维数组 数组的长度和元素都有
int[][] a = {{12, 34}, {21, 43, 54}, {12, 53, 76}, {21, 54}};
int[][] b = {{16, 34, 34}, {87, 96, 66}};
// 动态创建二维数组
int[][] c = new int[3][];
int[] arr0 = {12, 34};
int[] arr1 = {21, 43, 54};
int[] arr2 = {12, 53, 76};
c[0] = arr0;
c[1] = arr1;
c[2] = arr2;
for (int[] x : c) {
for (int y : x) {
System.out.print(y + "\t");
}
System.out.println();
}
for (int i = 0; i < c.length; i++) {
for (int j = 0; j < c[i].length; j++) {
System.out.print(c[i][j] + "\t");
}
}
}
}
🆚🆚运行结果:
12 34
21 43 54
12 53 76
12 34 21 43 54 12 53 76
8. 方法
在Java中,方法(函数)是代码组织的基本单位,方法封装了一段特定的代码,这段代码执行某个特定的任务或操作。
8.1 方法的好处
- 实现对相同代码的复用
- 将重复的代码段封装成方法,就可以在整个程序中的任何位置调用它,而无需重复编写相同的代码。可以提高代码的复用性、可维护性和可读性。
- 使程序逻辑清晰
- 通过将程序逻辑分解为多个小方法,我们可以更容易地理解每个部分的职责和工作原理,从而使整个程序的结构更加清晰。
- 实现细粒度设计
- 细粒度设计是一种将程序拆分成多个小模块或组件的方法,每个模块或组件都具有明确的功能和接口。方法是实现细粒度设计的基本单元之一。
- 细粒度设计还有助于提高代码的可测试性。由于每个方法都具有明确的输入和输出,我们可以更容易地为它们编写单元测试,以确保它们按预期工作。这有助于我们在开发过程中发现并修复错误,提高代码的质量和可靠性。
8.2 方法的定义
方法头(方法签名或方法声明)由五个部分组成。方法定义的语法格式如下:
[访问修饰符] <返回值类型> <方法名>([参数列表]) [异常列表] {
// 方法体
[return 语句(如果返回值类型不是void)];]
}
// 使用中括号[]包括的部分是可选项,使用尖角号<>包括的部分是必填项。
8.3 方法的调用
定义了一个方法之后,这个方法本身不会自动运行。它必须被另一个方法(通常是main
方法或其他非静态方法)显式地调用才能执行。方法调用的基本语法如下:
方法名(参数1, 参数2, ..., 参数N);
调用一个方法时,需要遵循以下规则:
- 方法名:必须使用定义该方法时所用的确切名称来调用它。
- 参数列表:如果方法需要参数,必须在调用时提供与定义时相匹配的参数。这包括参数的个数、类型和顺序。如果参数类型不匹配,或者参数的个数不正确,编译器将报错。
- 实参(Actual Parameters):当调用一个方法并传递参数时,提供的具体值被称为实参。这些实参将被用于方法内部的计算或操作。
8.4 方法返回值
- 返回值类型不是void:对于返回值类型不是
void
的方法,必须确保方法体内有一个与返回值类型相匹配的return
语句来返回一个值。 - 返回值类型是void:对于返回值类型是
void
的方法,return
语句后面不需要跟任何表达式,但可以用来提前结束方法的执行。
8.5 方法调用和内存结构
- 方法的存放位置
- 在Java程序的源代码中,方法被定义在类中。
- 当Java程序被编译后,方法的字节码被存储在
.class
文件中。 - 当Java虚拟机(JVM)加载
.class
文件时,它会把类的信息(包括方法)加载到方法区(Method Area)中,也被称为永久代(PermGen,在Java 8之前)或元空间(Metaspace,在Java 8及以后)。方法区方法的调用和执行存储了类的元数据,包括方法信息、常量池等。
- 方法的调用和执行
- 当一个方法被调用时(无论是从主方法、其他方法还是通过事件等),JVM会在Java栈(Java Stack)中为该方法的执行创建一个新的栈帧(Stack Frame)。
- 栈帧包含了方法的局部变量、操作数栈、指向当前方法所属类的常量池的引用等信息。
- 方法在栈帧中执行,执行过程中可能会涉及到操作局部变量、调用其他方法等操作。
- 如果方法A调用了方法B,而方法B又调用了方法A(没有合适的终止条件),这会导致无限递归,最终会因为栈空间耗尽而抛出
StackOverflowError
。
- 栈溢出和递归
- 栈溢出(StackOverflowError)通常是由于递归调用过深、方法调用过多导致栈空间耗尽而引发的。
- 在编写递归方法时,需要特别注意递归的终止条件,以确保递归调用能够正常结束。
8.7 方法参数传递
在Java中,方法参数的传递是通过值传递(pass-by-value)进行的。
- 基本数据类型传递:传递的是参数值的副本,在方法内部对参数值的修改不会影响原始变量。
- 引用数据类型传递:传递的是该对象在内存中的引用地址的副本,而不是对象本身。
8.8 方法的重载
方法的重载(Overloading)是指在同一个类中,使用相同的方法名,定义多个不同方法的机制。方法的重载有以下几个要点:
- 方法名必须相同。
- 参数列表必须不同(参数的类型、参数的个数或参数的顺序不同)。
- 返回类型可以相同,也可以不相同(返回类型不参与重载)。
- 访问修饰符和异常类型可以相同,也可以不相同(访问修饰符和异常类型不参与重载)。
8.9 可变参数(不定长参数)
可变参数(Varargs,即Variable-length Arguments)是一个允许在调用方法时传入任意数量参数的特性。这种特性使得方法可以接受可变数量的参数,而不需要为每种可能的参数数量重载方法。以下是可变参数定义的语法格式:
returnType methodName(type... parameters) {
// 方法体
}
可变参数的规则如下:
- 如果方法中,有可变参数,还有其他参数,可变参数必须是最后出现,并且只能有一个可变参数。
- 可变参数是兼容数组类型参数的,但是数组类型参数不兼容可变参数。
- 能匹配定长的方法时,优先匹配定长方法,不定参数的方法是最后被选择的。
- 可变参数方法和数组方法不能重载。
8.10 return的用法
return
语句的两种主要用法:
- 在有返回值的方法中,返回方法指定类型的值,同时结束方法执行。
- 在返回值为
void
的方法中,用来结束当前方法,回到主调方法,后面的语句都不会再执行。
9. 命名规范
Java中的命名规则主要包括以下几点:
- 包名:全部小写,用点分隔符
.
来分隔各级目录,通常为反域名命名法,例如com.example.mypackage
。 - 类和接口名:每个单词的首字母都大写(大驼峰式命名法,又称Pascal命名法),例如
MyClass
或IMyInterface
。 - 对象名:首字母小写,遇到单词就大写(小驼峰式命名法,又称camelCase命名法),例如
object
或myObjectName
。 - 方法名:首字母小写,遇到单词就大写(小驼峰),例如
myMethod()
或getNumber()
。 - 变量名:首字母小写,遇到单词就大写(小驼峰),例如
sum
或myVariable
。 - 常量名:全部大写,单词间用下划线
_
分隔,例如PI
或MAX_VALUE
。
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
以上内容是关于Java基本语法的基础知识,希望对初学者或再次学习者有所帮助,基础打扎实,不怕风吹雨打!如果以上内容有错误或者内容不全,望大家提出!我也会继续写好每一篇博文!
👍👍👍
待续未完
🙊🙊🙊
欢迎观看和提问!!!
👏👏👏
下一篇:Java基础:面向对象(二)