第一章 Java 基础
1. Hello World程序
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
2. 数据类型
Java提供了多种数据类型,包括基本数据类型和引用数据类型。基本数据类型用于存储简单的数据值,而引用数据类型用于存储对象的引用。
2.1 基本数据类型
Java的基本数据类型包括整数类型、浮点类型、字符类型和布尔类型。每种基本数据类型都有固定的字节大小和表示范围。
数据类型 | 字节大小 | 数据范围 |
---|---|---|
byte | 1 | -128 到 127 |
short | 2 | -32,768 到 32,767 |
int | 4 | -2,147,483,648 到 2,147,483,647 |
long | 8 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
float | 4 | 单精度浮点数,可精确到小数点后7位 |
double | 8 | 双精度浮点数,可精确到小数点后15位 |
char | 2 | Unicode字符,范围为 ‘\u0000’ 到 ‘\uffff’ |
boolean | 1 | true 或 false |
2.2 自动类型转换和强制类型转换
在Java中,基本数据类型之间存在自动类型转换和强制类型转换的机制。
-
自动类型转换:当将一个小范围的数据类型赋值给一个大范围的数据类型时,会发生自动类型转换不会丢失精度。类型转换从小到大的顺序如下:
byte
->short
->int
->long
->float
->double
整数类型自动提升为更大的整数类型:
byte
->short
->int
->long
浮点数类型的自动浮点数类型:
float
->double
如果将一个较大范围的数据类型转换为较小范围的数据类型,会发生数据丢失的情况,需要进行强制类型转换。
-
强制类型转换:将一个大范围的数据类型转换为小范围的数据类型,强制类型转换可能会导致数据丢失或溢出。
例如,将一个
long
类型的数据转换为int
类型,将丢失long
类型的高32位数据。将一个
double
类型的数据转换为int
类型,将丢失double
类型的小数部分。
2.3 引用数据类型
Java中的引用数据类型用于存储对象的引用,而不是直接存储对象本身。引用数据类型包括类(Class)、接口(Interface)和数组(Array)。
- 类(Class):类是一种自定义的数据类型,用于创建对象。类定义了对象的属性和方法,并可以根据类创建多个对象。
class Person {
String name;
int age;
void display() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
- 接口(Interface):接口是一种抽象数据类型,定义了一组方法的规范。类可以实现接口,并提供实现接口中定义的方法。
interface Drawable {
void draw();
}
- 数组(Array):数组是一种用于存储多个相同类型数据的容器。数组可以是一维、二维或多维的,并且具有固定的大小。
int[] numbers = {
1, 2, 3, 4, 5};
String[] names = {
"Alice", "Bob", "Charlie"};
引用数据类型在栈内存中存储对象的引用地址,而对象本身存储在堆内存中。通过引用操作对象的属性和调用对象的方法。
Person person = new Person();
person.name = "Alice";
person.age = 25;
person.display(); // 调用对象的方法
需要注意的是,引用数据类型的赋值实际上是将引用复制给另一个引用变量,而不是复制对象本身。
Person person1 = new Person();
Person person2 = person1; // 将person1的引用复制给person2
2.4 标识符命名规范和注释
在Java中,标识符用于命名变量、方法、类等程序元素。标识符必须遵循一定的命名规范:
- 标识符由字母、数字、下划线和美元符号组成。
- 标识符的第一个字符不能是数字。
- 标识符区分大小写。
- 标识符不能是Java的关键字或保留字。
3. 变量和常量
3.1 变量
变量是用于存储数据的内存位置。在Java中,变量具有特定的类型,并且可以在程序执行过程中改变其值。
-
声明变量
在Java中,声明变量需要指定变量的类型和名称。语法格式如下:
type variableName;
其中,
type
表示变量的数据类型,variableName
表示变量的名称。例如,声明一个整数变量:int age;
-
初始化变量
变量可以在声明时初始化,也可以在稍后的代码中进行初始化。初始化变量即为变量赋予初始值。示例:
-
int age = 25; // 在声明时初始化变量
或者:
int age; // 声明变量 age = 25; // 在稍后的代码中初始化变量
需要注意的是,赋值时整数类型默认类型是int,浮点类型默认为double。示例:
// 错误示例:直接将 int 类型的字面值赋值给 byte 类型的变量 byte age = 25; // 编译错误,需要进行类型转换 // 正确示例:使用后缀进行赋值 byte age = 25B; // 使用 B 后缀表示字节型字面值,正确赋值
Java 还支持使用后缀表示特定类型的字面值:
B
后缀:用于表示字节型(byte
)。S
后缀:用于表示短整型(short
)。L
后缀:用于表示长整型(long
)。F
后缀:用于表示单精度浮点型(float
)。D
后缀:用于表示双精度浮点型(double
)。双精度浮点数字面值默认为double
类型,但可以使用D
后缀显式指定。
-
-
变量的作用域
变量的作用域指的是变量在程序中可见的范围。在Java中,变量的作用域可以是方法内部、方法参数、代码块或类的成员变量。
- 方法内部变量:在方法内部声明的变量只在方法内部可见。
void calculate() { int result = 0; // 方法内部变量 // ... }
- 方法参数:方法参数是在方法声明中指定的变量,用于接收调用方法时传递的参数。
void printName(String name) { // ... }
- 代码块变量:在代码块中声明的变量只在代码块内部可见。
{ int count = 0; // 代码块变量 // ... }
- 类的成员变量:类的成员变量属于类的实例,可以在整个类中访问。
class Person { String name; // 类的成员变量 // ... }
-
类变量和实例变量
在Java中,类的成员变量可以分为类变量和实例变量。
- 类变量:类变量是使用
static
关键字声明的变量,它属于整个类而不是类的实例。类变量在类的所有实例之间共享。
class Counter { static int count; // 类变量 // ... }
- 实例变量:实例变量是在类中声明的非静态变量,每个类的实例都有自己的一份实例变量。
class Person { String name; // 实例变量 int age; // 实例变量 // ... }
- 类变量:类变量是使用
3.2 常量
常量是在程序执行过程中不可更改的值。在Java中,使用关键字final
声明常量。常量一旦被赋值后,其值不能再被修改。
-
声明常量
声明常量需要使用
final
关键字,并遵循命名规范。通常使用大写字母表示常量的名称。final dataType CONSTANT_NAME = value;
其中,
dataType
表示常量的数据类型,CONSTANT_NAME
表示常量的名称,value
表示常量的初始值。示例:final double PI = 3.14159;
-
常量的命名规范
常量的命名规范与变量的命名规范相似,但常量通常使用全大写字母,并使用下划线分隔单词。
final int MAX_VALUE = 100;
-
常量的使用
常量可以在程序中使用,但不能修改其值。常量的主要作用是定义程序中的固定值,提高代码的可读性和维护性。
final int MAX_VALUE = 100; int number = 50; if (number > MAX_VALUE) { // ... }
4. 常见的标识符命名场景
- 类名接口名:使用大写字母开头的驼峰命名法(CamelCase),例如
Person
,Student
,Runnable
,Comparable
。类名通常用于表示具体的对象类型,接口名通常用于表示一组相关的操作或功能。 - 方法名:使用小写字母开头的驼峰命名法,例如
getName()
,calculateTotal()
,isInitialized()
。方法名应具有描述性,能清晰地表达方法的功能或操作。 - 变量名:使用小写字母开头的驼峰命名法,例如
age
,firstName
,totalCount
。变量名应具有描述性,能清晰地表示变量所代表的含义。 - 常量名:通常使用全大写字母,单词间用下划线分隔的命名法,例如
MAX_VALUE
,PI
,DEFAULT_TIMEOUT
。常量名应具有描述性,表达常量的含义和用途。 - 包名:使用小写字母,单词间用点号(.)分隔的命名法,例如
com.example.myproject
,org.openai.chatbot
. 包名应具有唯一性和可读性,能够反映所包含类的层次结构和作用域。 - 构造函数:与类名相同,使用大写字母开头的驼峰命名法,例如
Person()
,Student()
,MyClass()
。构造函数用于创建对象,与类名相同能够清晰地表示所创建的对象类型。 - 枚举类型:与类名相同的规则适用于枚举类型,使用大写字母开头的驼峰命名法,例如
Color.RED
,DayOfWeek.MONDAY
,Size.SMALL
。
5. 运算符
运算符是用于执行特定操作的符号或关键字。在Java中,有多种类型的运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符和其他运算符。本节将介绍这些运算符的使用和优先级。
- 算术运算符
运算符 | 描述 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取模运算 |
- 赋值运算符
运算符 | 描述 |
---|---|
= | 简单赋值 |
+= | 加法赋值 |
-= | 减法赋值 |
*= | 乘法赋值 |
/= | 除法赋值 |
%= | 取模赋值 |
- 比较运算符
运算符 | 描述 |
---|---|
== | 相等 |
!= | 不等 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
- 逻辑运算符
运算符 | 描述 |
---|---|
&& | 短路与 |
|| | 短路或 |
! | 非 |
- 位运算符
运算符 | 描述 |
---|---|
& | 位与 |
| | 位或 |
^ | 位异或 |
~ | 位非 |
<< | 左移 |
>> | 右移 |
>>> | 无符号右移 |
- 关系运算符
运算符 | 描述 |
---|---|
== | 相等 |
!= | 不等 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
- 递增和递减运算符
运算符 | 描述 |
---|---|
++ | 递增 |
– | 递减 |
- 三目运算符
运算符 | 描述 |
---|---|
? : | 条件运算符(三目运算符) |
- 运算符的优先级
在表达式中,运算符具有不同的优先级。具有较高优先级的运算符会先于具有较低优先级的运算符进行计算。如果存在相同优先级的运算符,会根据结合性决定计算顺序。
下表按照优先级从高到低列出了常见的运算符:
运算符 | 描述 |
---|---|
() | 括号(用于改变运算符的优先级) |
++, – | 递增和递减运算符 |
! | 逻辑非运算符 |
*, /, % | 乘法、除法和取模运算符 |
+, - | 加法和减法运算符 |
<<, >>, >>> | 左移、右移和无符号右移运算符 |
<, <=, >, >= | 关系运算符 |
==, != | 相等性运算符 |
& | 位与运算符 |
^ | 位异或运算符 |
| | 位或运算符 |
&& | 短路与运算符 |
|| | 短路或运算符 |
?: | 条件运算符(三目运算符) |
=, +=, -=, *=, /=, %= | 赋值运算符 |
以上表格中的运算符顺序是按照优先级从高到低排列的,具有相同优先级的运算符按照结合性从左到右计算。
请注意,赋值运算符(包括简单赋值运算符和复合赋值运算符)是右结合性的,而不是按照从左到右的结合性。
右结合性意味着赋值运算符计算顺序是从右往左进行的。
例如,考虑以下示例:
int a = 5;
int b = 10;
int c = 15;
c = a = b;
System.out.println("a = " + a); // 输出结果:a = 10
System.out.println("b = " + b); // 输出结果:b = 10
System.out.println("c = " + c); // 输出结果:c = 10
6. 控制流程
在编程中,流程控制语句用于控制程序的执行流程,根据条件进行判断和重复执行特定的代码块。Java 提供了多种流程控制语句,包括条件语句、循环语句和跳转语句。
6.1 条件语句
条件语句根据条件的真假来选择性地执行代码块。
-
if-else 语句
if (条件1) { // 如果条件1为真,执行这里的代码块 } else if (条件2) { // 如果条件1不满足,且条件2为真,执行这里的代码块 } else { // 如果前面的条件都不满足,执行这里的代码块 }
条件1
条件2
是一个布尔表达式,用于判断执行哪个代码块。- 如果
条件1
为真,将执行if
代码块中的代码。 - 如果
条件1
为假条件2
为真,将执行else if
代码块中的代码。 - 如果
条件1
为假条件2
为假时,将执行else
代码块中的代码。
-
switch 语句
switch (表达式) { case 值1: // 如果表达式的值等于值1,执行这里的代码块 break; case 值2: // 如果表达式的值等于值2,执行这里的代码块 break; default: // 如果表达式的值与前面的值都不匹配,执行这里的代码块 break; }
- 根据
表达式
的值,选择匹配的case
分支执行相应的代码块。 - 如果找到匹配的分支,则执行该分支的代码块,并使用
break
语句跳出switch
语句。 - 如果没有找到匹配的分支,则执行
default
分支的代码块(可选)。 - 需要注意的是,如果缺少
break
语句,执行匹配的语句后会向下顺序执行。
- 根据
6.2 循环语句
循环语句用于重复执行特定的代码块,直到满足退出条件为止。
-
while 循环
while (条件) { // 只要条件为真,重复执行这里的代码块 }
条件
是一个布尔表达式,用于判断是否继续执行循环。- 只要
条件
为真,就会重复执行while
循环中的代码块。 - 在循环执行代码块之前和之后,都会检查
条件
的值。如果条件
为假,则退出循环,继续执行后续的代码。
-
do-while 循环
do { // 先执行这里的代码块 } while (条件);
条件
是一个布尔表达式,用于判断是否继续执行循环。- 先执行
do
代码块中的代码,然后再检查条件
的值。 - 只要
条件
为真,就会重复执行do-while
循环中的代码块。 - 在循环执行代码块之前和之后,都会检查
条件
的值。如果条件
为假,则退出循环,继续执行后续的代码。
-
for 循环
for (初始化语句; 条件; 更新语句) { // 只要条件为真,重复执行这里的代码块 }
初始化语句
用于初始化循环控制变量。条件
是一个布尔表达式,用于判断是否继续执行循环。更新语句
用于更新循环控制变量的值。- 在执行完
初始化语句
后,先检查条件
的值。只要条件
为真,就会重复执行for
循环中的代码块。 - 在每次循环结束后,执行
更新语句
来更新循环控制变量的值。
6.3 跳转语句
跳转语句用于在程序执行过程中改变执行的顺序。
- break 语句
break
语句用于跳出当前的循环或 switch
语句。
while (条件) {
if (某个条件) {
break; // 跳出循环
}
// 其他代码
}
switch (表达式) {
case 值1:
// 代码块
break; // 跳出 switch 语句
case 值2:
// 代码块
break; // 跳出 switch 语句
default:
// 代码块
break; // 跳出 switch 语句
}
- continue 语句
continue
语句用于跳过当前循环中剩余的代码,直接进入下一次循环。
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过当前循环的剩余代码,进入下一次循环
}
// 其他代码
}
-
return 语句
return
语句用于从方法中返回值,并终止方法的执行。
public int calculateSum(int a, int b) {
int sum = a + b;
return sum; // 返回 sum 的值并结束方法的执行
}
- 在方法中使用
return
语句可以返回一个值,并将该值传递给调用方法的地方。 return
语句也可以用于提前终止方法的执行,即使没有返回值。
7. 数组
数组是一种用于存储多个相同类型元素的数据结构。在 Java 中,数组具有固定长度,并且可以在声明时或运行时初始化。使用数组可以方便地管理和操作大量数据。
7.1 声明和初始化数组
在 Java 中,声明数组需要指定数组的类型和长度。数组的长度确定后,无法再次更改。
-
声明数组
// 声明一个整型数组 int[] numbers; // 声明一个字符串数组 String[] names;
-
静态初始化
静态初始化是指在声明数组的同时,直接为数组元素赋值。
// 静态初始化整型数组
int[] numbers = {
1, 2, 3, 4, 5};
// 静态初始化字符串数组
String[] names = {
"Alice", "Bob", "Charlie"};
- 动态初始化
动态初始化是指在声明数组后,通过指定数组长度来分配内存空间,并逐个为数组元素赋初值。
// 动态初始化整型数组
int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;
// 动态初始化字符串数组
String[] names = new String[3];
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";
7.2 访问数组元素
数组元素的访问通过索引(下标)来实现,索引从 0 开始,依次递增。
int[] numbers = {
1, 2, 3, 4, 5};
int firstNumber = numbers[0]; // 访问第一个元素
int thirdNumber = numbers[2]; // 访问第三个元素
7.3 遍历数组
可以使用 length
属性获取数组的长度,该属性返回数组中元素的个数。
再使用循环结构遍历数组,访问数组的每个元素。
- for 循环遍历数组
int[] numbers = {
1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
int number = numbers[i]; // 获取当前元素
// 执行相应操作
}
- 增强型 for 循环遍历数组
增强型 for 循环(也称为 for-each 循环)适用于遍历数组的所有元素。
int[] numbers = {
1, 2, 3,4, 5};
for (int number : numbers) {
// 执行相应操作,number 为当前元素的值
}
7.4 多维数组
除了一维数组,Java 还支持多维数组,即数组中包含数组。
-
声明和初始化多维数组
// 声明一个二维整型数组 int[][] matrix; int[] matrix2[]; // 声明一个三维字符串数组 String[][][] cube;
多维数组的初始化可以通过嵌套的方式进行。
// 静态初始化二维整型数组 int[][] matrix = { { 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9}}; // 静态初始化三维字符串数组 String[][][] cube = { { { "A1", "A2"}, { "A3", "A4"} }, { { "B1", "B2"}, { "B3", "B4"} } };
-
访问多维数组元素
通过多个索引来访问多维数组的元素。
int[][] matrix = { { 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9}}; int element = matrix[1][2]; // 访问第二行第三列的元素
-
遍历多维数组
可以使用嵌套的循环结构遍历多维数组的所有元素。
int[][] matrix = { { 1, 2, 3}, { 4, 5, 6}, { 7, 8, 9}}; for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { int element = matrix[i][j]; // 获取当前元素 // 执行相应操作 } }
7.5 数组的常见操作
在使用数组时,还可以进行一些常见的操作,如复制数组、排序数组等。
-
复制数组
可以使用
System.arraycopy()
方法或使用循环逐个复制数组元素来复制数组。int[] source = { 1, 2, 3, 4, 5}; int[] target = new int[source.length]; // 使用 System.arraycopy() 方法复制数组 System.arraycopy(source, 0, target, 0, source.length); // 使用循环逐个复制数组元素 for (int i = 0; i < source.length; i++) { target[i] = source[i]; }
-
排序数组
可以使用
Arrays.sort()
方法对数组进行排序。int[] numbers = { 5, 3, 1, 4, 2}; Arrays.sort(numbers); // 对数组进行排序
7.6 数组的注意事项
- 在使用数组前,应确保数组已经被正确初始化,没有正确初始化数组或访问了一个为
null
的数组引用,会导致空指针异常的发生。 - 数组在声明时需要指定类型和长度,且长度一旦确定后无法更改。
- 数组可以存储任意类型的元素,包括基本数据类型和引用数据类型,但是只能存储一种数据类型。
- 二维数组实际上一维数组,数组中每个元素存放指向一维数组的引用。
8. 方法
方法是一段封装了特定功能的代码块,可以通过方法名和参数列表来调用执行。在 Java 中,方法用于实现代码的模块化、重用和组织。本节将介绍方法的声明、调用、参数、返回值、重载和递归等相关内容。
8.1 方法的声明和定义
方法的声明和定义包括方法名、参数列表、返回类型和方法体的结构。方法的声明告诉编译器方法的存在和如何调用它,方法的定义则提供了方法的具体实现。
// 方法的声明
返回类型 方法名(参数列表) {
// 方法体
// 执行相应操作
}
// 方法的定义
返回类型 方法名(参数列表) {
// 方法体
// 执行相应操作
return 返回值; // 可选,如果方法有返回值的话
}
其中:
- 返回类型指定了方法执行后的返回结果类型,可以是基本数据类型或引用数据类型。如果方法没有返回值,返回类型应为
void
。 - 参数列表是一组以逗号分隔的参数,用于接收调用方法时传递的值。参数列表可以为空,也可以包含一个或多个参数。
- 方法体包含了实现方法功能的代码块。
示例:
// 无返回值的方法,不带参数
public void greet() {
System.out.println("Hello, world!");
}
// 有返回值的方法,带参数
public int add(int a, int b) {
int sum = a + b;
return sum;
}
8.2 方法的调用
调用方法是通过方法名和参数列表来执行方法的过程。调用方法时,根据方法名和参数列表的匹配来确定要调用的方法。
// 调用无返回值的方法
方法名(参数列表);
// 调用有返回值的方法
数据类型 变量名 = 方法名(参数列表);
示例:
// 调用无返回值的方法
greet();
// 调用有返回值的方法
int result = add(3, 5);
System.out.println("Result: " + result);
8.3 方法的参数
方法的参数用于接收调用方法时传递的值。参数列表是一组以逗号分隔的参数,每个参数由参数类型和参数名组成。方法在执行时可以使用这些参数进行计算和处理。
// 无参数的方法
public void methodName() {
// 执行相应操作
}
// 带参数的方法
public void methodName(参数类型 参数名) {
// 执行相应操作,可以使用参数进行计算和处理
}
示例:
// 带参数的方法
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
// 调用带参数的方法
greet("John");
8.4 方法的返回值
方法的返回值是方法执行后的结果,可以是基本数据类型或引用数据类型。在方法定义时,通过返回类型来指定方法的返回值类型。方法可以使用 return
语句将结果返回给调用方。
// 无返回值的方法
public void methodName() {
// 执行相应操作
}
// 有返回值的方法
public 返回类型 methodName() {
// 执行相应操作
return 返回值;
}
示例:
// 有返回值的方法
public int add(int a, int b) {
int sum = a + b;
return sum;
}
// 调用有返回值的方法
int result = add(3, 5);
System.out.println("Result: " + result);
8.5 方法的重载
方法重载是指在同一个类中可以定义多个同名但参数列表不同的方法。通过方法的参数列表的不同来区分不同的方法,可以根据不同的需求来实现类似功能但具有不同参数的方法。
方法重载的条件:
- 方法名相同。
- 参数列表不同(参数个数、参数类型或参数顺序不同)。
示例:
// 重载的方法
public void greet() {
System.out.println("Hello!");
}
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
public void greet(String name, int age) {
System.out.println("Hello, " + name + "! You are " + age + " years old.");
}
8.6 方法的递归
方法递归是指在方法体内部调用自身的过程。递归方法可以用于解决需要重复执行相似操作的问题。在递归过程中,每次调用方法时都会将参数不断传递下去,直到达到递归终止条件才会停止递归。
递归方法必须包含递归终止条件,否则会导致无限递归,最终抛出 StackOverflowError
异常。
示例:
public int factorial(int n) {
if (n == 0 || n