介绍
简要说明
-
主要功能
注意事项
适用场景
- 云服务
重要概念
注释
- Java官方在整理一份实现整个“世界”的底层代码类库,即封装代码对外调用。
- Java不同的版本,本质是“迭代”和“修正”的过程,关注哪些“新增删除的功能”和“修订功能”。
- 我们应该“关注、学习、使用”这些公共标准基础类库,减少第三方类库的依赖。
- 所有“新功能/特性”、“语法规则”的底层都是封装好的代码类库,只不过有一些被设置为“默认导入”,即不需要手动导入代码类库。
JVM、JRE、JDK的关系
详细总结:Package-OracleJDK-问题总结-JVM、JRE、JDK的关系
编译与执行周期
在 Java 编程语言中,所有源代码首先以纯文本文件编写,以 .java 扩展名结尾。然后,javac 编译器将这些源文件编译为 .class 文件。.class 文件不包含处理器原生的代码;它包含字节码 — Java 虚拟机1 (Java VM) 的机器语言。然后,java 启动器工具使用 Java 虚拟机的实例运行您的应用程序。
由于 Java VM 在许多不同的操作系统上可用,因此相同的 .class 文件能够在 Microsoft Windows、Solaris™ 操作系统 (Solaris OS)、Linux 或 Mac OS 上运行。某些虚拟机(如 Java SE HotSpot 概览,在运行时执行其他步骤以提升应用程序的性能。这包括各种任务,例如查找性能瓶颈和重新编译 (到本机代码) 常用代码段。
第一阶段 - 基础知识
案例
package tech.magictable.softwareagent.service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 标记成SpringBoot的启动入口
public class App {
public static void main(String[] ages) {
SpringApplication.run(App.class, ages);
}
}
缩进
- 缩进是4个空格。
- 每行结束需要用分号
;结束。 - java用花括号
{}来界定代码范围,即局部作用范围或全局作用范围。
注释
- 用于在源代码中解释代码的作用,可以增强程序的可读性,可维护性。
- 注释不会被编译器包含在最终的可执行程序中,因此不会影响程序的运行。
Java注释分为三种类型:单行注释、多行注释、文档注释
- 单行注释:以双斜杠
//开始。 - 多行注释:以
/*开始,以*/结束。 - 文档注释:以
/**开始,以*/结束,通常出现在类、方法、字段的声明前面,用于生成代码文档,这种注释可以被javadoc命令提取并生成API文档。
// 单行注释
/*
*多行注释
*多行注释
*/
/**
*文档注释
*包含类、方法、字段的详细信息
*/
标识符
- 用来给类名、变量名、方法名等对象定义的有效字符序列,称为标识符。
标识符命名规范
- 所有的标识符是允许使用字母(
A~Z或者a~z)、数字(0~9)、美元符($)、或者下划线(_)构成。 - 不能有空格。
- 不能以数字开头。
- 严格区分大小写。
- 不能与“关键字”、“保留字”重名。
公共知识 - 代码命名规范:基础知识-规范-通用代码命名规范
Java 命名规范详细总结:Language-Java-JavaSE-热门推荐-命名规范
关键字
- 关键字是Java编程语言中的语法规则语句,具有特殊含义,用于定义程序的基本结构和元素。
- 关键字是Java语言的一部分,其数量有限且固定,不能更改或重新定义。
保留字
- 当前Java版本尚未使用,但以后版本可能会作为关键字使用的标识符被称为保留字。
- ==待完善==
| 标识符 | 说明 | 详情 |
|---|---|---|
| goto | ||
| const |
变量
- 计算机内存中的一块存储空间,是存储数据的基本单元。
- 变量是程序中用于存储数据的容器。
- 变量的值在程序执行过程中可以被改变。
- 变量的三要素:数据类型、变量名、数值
变量创建步骤
- 声明变量,即根据数据类型向在内存中申请空间。
- 赋值,即将数据存储到对应的内存空间。
案例
// <数据类型> <变量名> = <数值>;
int value = 100;
作用域/范围
- 变量、方法或类在程序中可以被访问和使用的范围。
- 决定了变量或方法的可见性,以及它们在内存中的生命周期。
作用域的分类
- 局部作用域:仅限于方法或代码块内部,生命周期短。
- 实例作用域:与对象实例相关联,生命周期覆盖整个对象。
- 类作用域:与类相关联,生命周期覆盖整个程序运行期间。
- 块作用域:仅限于代码块内部,用于临时变量管理。
运算符
- 运算符:对常量或变量进行操作的符号。
- Javad编程语言的运算符可分为一下6类:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件运算符(三元运算符)
运算符优先级
- 运算符有规定优先级别等级,来防止冲突。
详细总结:Language-Java-JavaSE-运算符优先级
数据类型
- Java是强类型语言,对变量具有严格的数据类型区分。
基本数据类型
- 变量传递时,传递的是数据,即在内存中硬拷贝了一份数据。
- 支持数据类型:字符整型
byte、短整型short、整型int、长整型long、单精度浮点型float、双精度浮点型double、布尔型boolean、字符型char
引用(对象)数据类型
- 引用类型存放的是某个对象在内存中的地址。
- 引用数据类型在被传递值时,传递的是内存地址,即软拷贝/软链接。
- 因此在修改值时,修改的都是同一个内存地址中的数据。
- 支持数据类型:字符串
String、数组Array、枚举Enum
注释
- 在
C或C++编程语言中通过指针操作内存中的元素,在Java中是通过“引用”。 - 在Java中一切被视为对象,但我们操作的标识符实际上是对象的一个引用。
详细总结:Language-Java-JavaSE-数据类型
数据类型转换
- Java是强数据类型的语言,因此在遇到不同数据类型同时操作时,需要进行数据类型转换。
- 是否满足转换要求,是根据数据类型的
存储位数能否能容下转换后所需的存储空间,若存储不足则会丢失精度。 - 满足数据类型兼容时,会自动进行
自动类型转换,若不兼容时需要手动进行强制类型转换。
提示
类型转换表
- 类型转换的兼容性和精度关系大致可以总结为以下表格(从小到大)。
- char -> int -> long -> float -> double
- byte -> short -> int -> long -> float -> double
隐式转换(自动类型转换)
- 自动类型转换发生在两种类型的兼容性和精度存在差异时,从较低精度(或范围较小)的类型向较高精度(或范围较大)的类型转换。
- 在多种数据类型的数据混合计算时,会自动将所有数据转换为容量最大的那种数据类型,然后再进行计算。
- 位数低的类型数据可以自动转换位位数高的类型数据。
- byte、short与char之间不会相互自动转换。
- byte、short、char可以进行计算,再计算时会首先转换为int类型。
- 提示:boolean类型不参与计算/转换。
案例
int i = 100;
double d = i; // 自动将int类型的i转换为double类型的d
显式转换(强制类型转换)
- 将容量大的数据类型转换为容量小的数据类型。
- 使用时要加上强制转换符
()。 - 强制类型转换可能会导致数据丢失或精度降低。
案例
double d = 3.14;
int i = (int)d; // 显式将double类型的d转换为int类型的i,小数部分会被舍弃
第二阶段 - 流程控制
顺序结构
- 程序从上到下逐行执行,中间没有任何判断或跳转。
分支结构
if
- 先执行
if语句中的条件。 - 若不满足条件,则会按顺序执行后续的
else if语句中的条件。 - 若
if和else if没有一个满足条件,则执行else语句。 - 优点:能更精确的指定每个分支的判断条件。
- 缺点:每个分支都需要手写判断条件。
- 注意事项:
else if是可以有多个。只执行其中最先满足条件的一个分支。 - 适用场景:适用于范围区间内的判断、不确定具体值、判断条件多。
格式
if (条件表达式) {
// 代码块
}
else if (条件表达式) {
// 代码块
}
...
else if (条件表达式) {
// 代码块
}
else {
// 代码块
}
案例
int score = 60; // 分数
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 70) {
System.out.println("良好");
} else if (score >= 60) {
System.out.println("及格");
} else if (score < 60) {
System.out.println("不及格");
} else {
System.out.println("输入的分数有错误");
}
switch
case语句可以有一个或多个。- 若
case语句中的值有与switch语句的变量或表达式一致,则执行代码块。 - 若没有一个
case语句的值被执行,则执行default语句。 - 优点:不需要为每个分支都手动写判断语句,减少代码量。
- 缺点:只能与确定的值进行判断是否相等。
- 注意事项:
case语句的值不能重复。 必须在case语句和default语句的代码块结尾添加break语句,否则会继续运行后续代码(称为穿透问题),直到碰到break语句为止。 - 适用场景:适用于确定值的判断、条件必须确定、只需判断是否一致。
格式
switch (<变量或表达式>) {
case <值1>:
// 代码块
break;
case <值2>:
// 代码块
break;
...
// 若前面的`case`判断都不正确则执行`default`语句
default:
// 代码块
break;
}
案例
int userInput = 2; // 用户输入
switch (userInput) {
case 1:
System.out.println("创建智能体");
break;
case 2:
System.out.println("删除智能体");
break;
case 3:
System.out.println("修改智能体");
break;
case 4:
System.out.println("查询智能体");
break;
default:
System.out.println("暂无此功能");
break;
}
循环结构
for
- 在编程中用于遍历序列(如列表、元组、字符串)或其他可迭代对象,并在每次迭代中执行一段代码。
- 优点:语法简洁、清晰。
- 缺点:不能灵活更改迭代次数或条件、不支持无限循环。
- 注意事项:避免死循环。
- 适用场景:循环次数明确的情况、遍历序列类型、遍历字典、遍历集合、条件构成等差序列。
格式
for (<数据类型> <变量初始化>; <循环条件>; <变量迭代>) {
// 代码块
}
案例
public class StartUp {
public static void main(String[] ages) {
System.out.println("爱你三千遍");
int count = 3000;
for (int x = 0, y = count ; x < count; x++, y--) {
System.out.println("第" + (x+1) + "次:我爱你,还剩下" + (y-1) + "遍。");
}
}
}
for-each
- 使用for-each循环遍历数组或集合元素时,不用获取长度和索引来访问元素,该语句会自动遍历每一个元素。
- 优点:简洁易读的方式来遍历集合或数组,避免了
for循环的索引管理。 - 缺点:不能对数组或集合索引的直接访问或修改,需要隐式地创建迭代器对象导致比
for循环慢。 - 注意事项:
- 适用场景:遍历集合或数组中的所有元素、处理只读集合、代码简洁性和可读性要求高。
格式
// 自增,且获取的是迭代部分的数值,而不是下标。
for (<数据类型> <变量> : <迭代部分>) {
// 代码块
}
案例
public class StartUp {
public static void main(String[] ages) {
String[] data = {"看电影", "打游戏", "旅游"};
// 循环遍历,拿到其中的值
for (String i : data) {
System.out.println("今天和智能体一起" + i);
}
}
}
while
- 首先检查其后的条件表达式(布尔表达式)。
- 如果该条件表达式的结果为
true,则执行循环体中的代码块。 - 执行完代码块后,再次回到循环的开头,重新检查条件表达式。
- 如果条件表达式仍为
true,则继续执行代码块;如果为false,则退出循环。 - 优点:简单直接,能够清晰地表达“当满足某个条件时,重复执行某段代码”的逻辑。
- 缺点:循环条件、循环体、以及可能存在的步进操作(改变循环条件的状态)分散在代码的不同位置,对于复杂的循环逻辑,可能不太容易跟踪和理解。
- 注意事项:需要特别关注条件表达式的设计,以确保循环能够在某个时刻自然结束,避免进入无限循环(死循环)。
- 适用场景:循环次数不确定的情况、等待某个条件成立、游戏或模拟中的实时更新、与用户交互。
格式
<数据类型> <变量初始化>;
while (<循环条件>) {
//代码块
<变量迭代>;
}
案例
// 导入接收输入的类
import java.util.Scanner;
public class StartUp {
public static void main(String[] ages) {
// 实例化接收输入
Scanner scanner = new Scanner(System.in);
// 提示信息
System.out.println("Agent:" + "你好,我是你的专属智能体,很高兴遇见你!");
// 循环代码主体
while (true) {
// 提示信息
System.out.print("User:");
// 接收用户输入
String userInput = scanner.next();
// 判断是否退出循环
if(userInput == "结束对话") {
break;
}
System.out.println("Agent:" + "有什么事情需要我帮忙吗?");
}
}
}
do-while
- 先执行一次循环代码块,然后才检查循环条件,若条件为
ture则继续执行,为false则跳出循环。 - 优点:至少执行一次,无论条件是否满足。
- 缺点:若循环代码块需要根据循环条件来选择是否执行,则可能导致不必要的操作和资源浪费。
- 注意事项:第一次不会执行判断条件,直接运行其中的代码。
- 适用场景:需要至少执行一次、需要一开始就先执行一次,循环次数不确定的情况。
格式
<数据类型> <变量初始化>;
do {
// 代码块
<变量迭代>;
}
while (<循环条件>);
案例
// 导入接收输入的类
import java.util.Scanner;
public class StartUp {
public static void main(String[] ages) {
// 实例化接收输入
Scanner scanner = new Scanner(System.in);
// 循环
do {
System.out.println("Agent:" + "你好,我是你的专属智能体,很高兴遇见你!");
// 提示信息
System.out.print("User:");
// 接收用户输入
String userInput = scanner.next();
// 判断用户意图
if (userInput == "结束对话") {
break;
}
} while (true);
}
}
结束语句
- 跳转语句是指打破程序的正常运行,跳转到其他部分的语句。
- 跳出语句有3种:
break、continue、return
案例
public class JumpStatementsExample {
public static void main(String[] args) {
// break 语句案例
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当 i 等于 5 时,跳出循环
}
System.out.println("i: " + i); // 输出 0 到 4
}
// continue 语句案例
for (int j = 0; j < 10; j++) {
if (j % 2 == 0) {
continue; // 当 j 是偶数时,跳过本次循环
}
System.out.println("j: " + j); // 输出 1, 3, 5, 7, 9
}
// return 语句案例
System.out.println("返回值: " + getValue()); // 输出 "返回值: 42"
}
public static int getValue() {
// 方法执行到 return 语句时结束,并返回 42
return 42;
}
}
break
break语句:结束整个循环。
continue
continue语句:结束本次循环,继续执行下一次循环。
return
return语句:结束该语句所在函数(方法)的执行,并在有指定返回值时返回对应数值。
第三阶段 - 面向对象
- 面向对象编程(OOP)主要用于提高代码的可读性、可维护性和可扩展性。
- 面向对象是描述某个事物在整个解决问题的步骤中的行为。
- 就是要提取现实世界对象的
特性和行为,抽象成属性(变量/常量)和方法(函数)。 - 在编写代码中,主要在编写类和函数,而(构造函数、函数重载、函数重写)、(封装、继承、多态)、(静态、最终、抽象、接口)、(包)这些功能是为了协助函数和类,将代码写的更加合理。
提示
| 现实世界(事物) | 类(模板) | 对象(实例) |
|---|---|---|
| 特性/状态 | 变量/常量 | 属性 |
| 行为/操作 | 函数 | 方法 |
交流约定
- 本文章的内容将按照函数、类、构造函数、函数重载、函数重写等词汇,尽可能不使用面向对象的词汇。
- 与他人交谈时需要按照面向对象的词汇交流,如方法、构造方法、对象等,面向对象的方式交流。
类(对象)
- 在现实中,过程是易变的,对象是相对不变的,类是对现实世界理解和抽象的一种方法,这种方法也被称为
面向对象编程(OOP)。 - 类是对象的模板或蓝图,可以理解为是对一类事物的归纳,定义了一组具有共同特性和行为的对象。
- 提取现实世界对象的
特性和行为,抽象成属性(变量/常量)和方法(函数),而属性(变量/常量)和方法(函数)都写在类里,这个类可以理解为对象。
要求
- 命名规范:大写首字母开头,多个单词组成时每个单词首字母大写;类名与与文件名保持一致。
注意事项
类只是一个模板,在被实例化之前,内存中并没有这个类的数据。- 如果类被实例化(调用/使用)时,将会在内存中创建一个区域来存储这个类。
- 实例化时可顺便赋值给一个变量,这个变量将被称为
对象,一个真实存在内存中的实体。 - 类可以被多次实例化(调用/使用),之间都是独立存在于内存中。
对象是类的实例,对象拥有多个特征和行为的实体。
关键字:class
类的创建
格式
[<访问修饰符>] class <类名> {
}
案例
// 类
public class StartUp {
// 函数
public static void session(String txt) {
// 代码块
}
}
类的调用(实例化类、创建对象)
格式
<类名数据类型> <对象名> = new <类名>([<参数>, ...]);
案例
StartUp startUp = new StartUp("String字符串数值");
函数(方法)
- 函数(方法)是一个命名的代码块,执行特定的任务。
- 函数封装特定功能的一段代码,可以通过调用来实现重复使用。
- 函数可以使代码更加模块化、可重用和易于维护。
- 减少代码冗余,提高复用性,提高可读性,提高可维护性,方便分工合作。
要求
- 命名规范:小写首字母开头,多个单词组成时每个单词首字母大写。
注意事项
- 函数里面不能再定义函数。一个函数只做一件事。
- 函数定义在类的内部。
参数
- 函数可以接受一个或多个输入值,这些输入值被称为
参数。 - 参数允许我们将数据传递给函数,以便在函数内部使用。
- **形式参数:**形参,写在函数上,用于规定参数
- **实际参数:**实参,写在调用时的函数上,用于传入参数。
- **参数传递的形式:**位置参数、不定长参数、
返回值
- 函数可以返回一个值。
- 返回值可以是任何数据类型,包括基本数据类型和对象类型。
- 如果函数不返回任何值,则其返回类型为
void类型。
函数的创建
格式
<访问修饰符> [static] <返回值类型> <函数名>([<数据类型> <形式参数名>, ...]) {
// 方法主体,代码块
// 根据<返回值类型>来返回对应的值。
[return <返回值>]
}
案例
/**
* 访问权限:public
* 返回值:data(String类型)
* 函数名:session
* 形参:txt(String类型)
* 形式参数是一个字符串类型的数组
*/
public String session(String txt) {
// 函数体中的代码块
// 返回值
return data;
}
构造函数(初始化函数)
- 类中的特殊函数(方法)。
- 主要是为了在实例化对象时顺便接收参数进行初始化所需变量。
要求
- 构造函数与类的(名称、访问权限)必须相同,没有返回值。
- 不是静态
static,没有返回值return。
注意事项
- 构造函数可以
重载,实现传入不同参数来执行不同构造函数。 this关键字为构造方法中的局部变量声明,提供在一个类中的其他函数能够获取到变量。- 构造函数不是手动调用的,是对象被
new实例化的时候被JVM调用。 - 若一个类没有定义构造函数,JVM在编译的时候会给这个类默认隐式的添加一个无参构造函数。若一个类手动定义构造函数,就会将“默认的无参构造函数”覆盖掉。
适用场景
- 创建对象时初始化属性、设置默认值、执行必要的初始化逻辑。
案例
// 类
public class StartUp {
String txt;
// 构造函数需要写在类中
// 参数是可选的
public StartUp(String txt) {
// 这里使用`this`关键字来更新全局变量的值
this.txt = txt;
}
}
函数的重载(编译时多态)
- 在一个类中定义多个名称相同的函数,根据传递的参数不同而选择不同的方法。
- 重载函数必须在同一个作用域内定义,通常是在同一个类或命名空间中。
- 当编译器看到对函数的调用时,它会根据提供的参数类型和数量在作用域内查找匹配的重载函数,如果找不到匹配的重载函数,编译器会报错。
要求
- 函数名必须相同。
- 参数列表(数据类型、参数个数、顺序)必须不同。
注意事项
- 函数中的(访问权限、返回值类型、参数名称)与函数的重载无关。
适用场景
- 构造函数重载、动态更改方法行为、处理不同类型或数量的参数。
案例
// 类
public class StartUp {
public void abc(String a) {
}
// 普通函数 - 数据类型不同
public void abc(int a) {
}
// 普通函数 - 参数个数不同
public void abc(int a, String b) {
}
// 普通函数 - 顺序不同
public void abc(String b, int a) {
}
}
函数的重写(覆盖)
- 方法重写允许子类提供与父类方法签名相同的方法,但具有不同的实现。
- 这种机制使得子类能够根据需要修改或扩展父类的行为。
- 重写是面向对象设计中的一个基本原则,符合开闭原则,即对扩展开放,对修改封闭。
要求
- 子类的方法(返回类型、参数列表<类型、顺序、个数>)与父类必须完全一致。
- 方法名只建议与父类的方法名一致。(因为重写只是为子类新添加一个方法,父类的方法依旧存在,只是方法名相同的情况下优先就近调用子类内的方法而已)
注意事项
- 提示(建议)使用
@Override注解可以明确表明一个方法是重写的,在编译时如果父类中没有找到被重写的方法,编译器会有报错提示。 - 子类重写父类的方法时,访问权限不能比父类中被重写的方法的访问权限更低。
- 若父类的方法被
private关键字修饰,则不能被重写。
适用场景
- 当需要修改或扩展父类的功能以适应子类的特定需求时。
- 当需要在不同的上下文中使用不同的实现时。
- 当需要隐藏或修改父类方法的实现细节时。
- 当需要实现多态性,使得同一个方法在不同对象上有不同的行为时。
案例
// 父类
class Animal {
// 父类中的方法
public void makeSound() {
System.out.println("动物发出声音");
}
}
// 子类
class Dog extends Animal {
// 重写父类的方法
@Override
public void makeSound() {
System.out.println("狗吠");
}
}
// 测试类
public class OverrideTest {
public static void main(String[] args) {
Animal myAnimal = new Animal(); // 创建父类对象
Animal myDog = new Dog(); // 创建子类对象,但以父类类型引用
myAnimal.makeSound(); // 输出 "动物发出声音"
myDog.makeSound(); // 输出 "狗吠",这里发生了多态,调用了子类重写的方法
}
}
面向对象特性 - 封装
- 主要目的是增强代码的安全性和可维护性,尽可能隐藏类(对象)的内部实现细节,控制对象的修改和访问的权限,仅对外公开必要的接口。
- 保证数据安全性、减少外界对类的依赖。
要求
- 将类的变量(属性)声明为私有(private),只能通过该类专门提供的函数来访问和修改。
- 提供声明为公共(public)的
访问函数和修改函数,专门用于读取和修改私有变量的值。
注意事项
- 为一个类中做好为变量或函数添加访问权限的关键字,定义好是否能被外部访问,减少外部对类的依赖。
- 外界不能直接访问对象(类)中的属性(变量),只能访问对象中创建的公共方法(函数),对象(类)会通过给属性添加
private私有访问权限关键字来限制外界访问。
适用场景
- 实现业务逻辑、隐藏实现细节、防止意外修改、数据保护、实现多态。
- 高内聚:类内部数据操作细节自己完成,不允许外部干涉。
- 低耦合:仅暴露方法给外部使用。
关键字:private(私有)、protected(受保护)、default(默认)、public(公共)
案例
public class Test {
// 成员变量(属性)
private int age; // 私有的年龄变量
// 构造函数
public Test() {
}
// 普通函数
public static void abc() {
}
// 类说明函数
public String toString() {
return ""
}
// 公共的,接收参数的函数 - 无返回值、接收参数`年龄`
public void setAge(int age) {
this.age = age
// 做好错误异常处理的代码
// 判断传入的参数是否合理
// 若不合理则执行相应的代码块
}
// 公共的,查询参数的函数 - 有返回值、返回查询结果`年龄`
public int getAge() {
return this.age
}
}
面向对象特性 - 继承
- 继承允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。
- 产生继承关系之后,子类可以使用父类中的属性和方法,子类也可以新添加自己的属性和方法。
要求
- 单根性:一个类只能有一个直接父类,有助于保持代码的清晰和可维护性。
- 传递性:类之间可以多级继承,属性和方法逐级叠加。
注意事项
- 每个类都继承自
Object类,该类是顶级父类,是所有类的始祖。 - 子类不能继承父类的构造函数,但可以通过
super关键字调用父类的构造方法。
适用场景
当多个类具有共同的属性和方法时,可以使用继承来复用代码。
当需要扩展已有类的功能时,可以通过继承来添加新的特性。
继承是实现多态性的基础,允许使用父类类型的引用来调用子类的实现。
在设计和实现框架或库时,继承可以用来创建可扩展的体系结构。
关键字:
extends
案例
// 父类
class Vehicle {
// 父类的属性
protected String brand = "未知品牌";
// 父类的方法
public void honk() {
System.out.println("车辆发出哔哔声");
}
}
// 子类
class Car extends Vehicle {
// 子类的新属性
private String modelName = "未知型号";
// 子类的方法
public void displayInformation() {
System.out.println("品牌: " + brand + ", 型号: " + modelName);
}
// 子类的构造函数
public Car(String brand, String modelName) {
this.brand = brand;
this.modelName = modelName;
}
}
// 测试类
public class InheritanceTest {
public static void main(String[] args) {
Car myCar = new Car("Toyota", "Corolla");
myCar.honk(); // 输出 "车辆发出哔哔声"(继承自父类的方法)
myCar.displayInformation(); // 输出 "品牌: Toyota, 型号: Corolla"(子类自己的方法)
}
}
this关键字
this关键字代表当前对象的引用,可以用来访问当前对象的成员变量和方法。- 格式:
this.<成员变量名>或this.<方法名>(参数列表)
要求
- 不需要特定的要求。
注意事项
- 当在一个类的构造函数中使用
this来调用另一个构造函数时,这个调用必须是该构造函数的第一条语句。 this和super不能在同一个构造函数中同时用来调用构造函数,因为它们都必须是第一条语句。
适用场景
- 当需要在方法或构造函数中明确指出当前对象的成员变量或方法时。
- 当需要在构造函数中调用同一个类的另一个构造函数时。
关键字:this
案例
// 一个简单的类,使用 this 关键字
class Person {
// 成员变量
private String name;
private int age;
// 构造函数
public Person(String name, int age) {
this.name = name; // 使用 this 来区分成员变量和构造函数参数
this.age = age;
}
// 另一个构造函数,使用 this 调用上面的构造函数
public Person(String name) {
this(name, 0); // 调用 Person(String name, int age) 构造函数
}
// 成员方法
public void displayInfo() {
System.out.println("Name: " + this.name + ", Age: " + this.age);
}
// 设置名字的方法
public void setName(String name) {
this.name = name; // 使用 this 来引用成员变量
}
// 设置年龄的方法
public void setAge(int age) {
this.age = age; // 使用 this 来引用成员变量
}
}
// 测试类
public class ThisKeywordTest {
public static void main(String[] args) {
Person person1 = new Person("Alice", 30);
person1.displayInfo(); // 输出 "Name: Alice, Age: 30"
Person person2 = new Person("Bob");
person2.displayInfo(); // 输出 "Name: Bob, Age: 0"
person2.setAge(25); // 使用 this 在 setAge 方法中设置 age 成员变量
person2.displayInfo(); // 输出 "Name: Bob, Age: 25"
}
}
super关键字
- 用于引用父类对象的一个引用。
- 在子类的构造函数中,
super用于调用父类的构造函数。
要求
- 只能在继承父类的子类中使用。
注意事项
- 当在子类的构造函数中使用
super调用父类的构造函数时,这个调用必须是该构造函数的第一条语句。 super和this不能在同一个构造函数中同时使用,因为它们都必须是第一条语句。
适用场景
- 当需要在子类中调用父类的构造函数时。
- 当需要在子类中访问被重写的父类方法或成员变量时。
关键字:super
案例
// 父类
class Animal {
protected String name;
// 父类的构造函数
public Animal(String name) {
this.name = name;
}
// 父类的方法
public void eat() {
System.out.println(name + " is eating.");
}
}
// 子类
class Dog extends Animal {
private String breed;
// 子类的构造函数,使用 super 调用父类的构造函数
public Dog(String name, String breed) {
super(name); // 调用父类的构造函数
this.breed = breed;
}
// 子类重写父类的方法
@Override
public void eat() {
super.eat(); // 调用父类的 eat 方法
System.out.println("The " + breed + " dog is eating.");
}
}
// 测试类
public class SuperKeywordTest {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", "Golden Retriever");
dog.eat(); // 输出 "Buddy is eating." 和 "The Golden Retriever dog is eating."
}
}
注释
super关键字 - 访问父类的构造函数
- 说明:
super关键字用于在子类的构造函数中调用父类的构造函数。 - 格式:
super(<参数列表>) - 适用场景:这确保了在创建子类对象时,父类的构造函数也会被执行,从而正确地初始化继承自父类的成员变量。
super关键字 - 访问父类的属性
- 说明:当子类和父类中定义了同名的属性时,可以使用
super关键字来明确指定访问的是父类中定义的属性。 - 格式:
super.<属性名> - 适用场景:这有助于在子类中区分同名的属性。
super关键字 - 访问父类的方法
- 说明:在子类中,如果需要调用父类中被重写的方法,可以使用
super关键字。 - 格式:
super.<方法名> - 适用场景:这在子类重写父类方法但仍然需要在某些情况下调用父类方法时非常有用。
// 父类
class Parent {
protected String name;
// 父类的构造函数
public Parent(String name) {
this.name = name;
}
// 父类的方法
public void printName() {
System.out.println("Parent's name: " + name);
}
}
// 子类
class Child extends Parent {
private String name; // 子类中同名的属性
// 子类的构造函数,使用 super 调用父类的构造函数
public Child(String parentName, String childName) {
super(parentName); // 调用父类的构造函数
this.name = childName; // 初始化子类的属性
}
// 子类重写父类的方法
@Override
public void printName() {
super.printName(); // 调用父类的方法
System.out.println("Child's name: " + this.name); // 输出子类的属性
}
}
// 测试类
public class SuperKeywordExample {
public static void main(String[] args) {
Child child = new Child("John", "Alice");
child.printName(); // 输出 "Parent's name: John" 和 "Child's name: Alice"
}
}
对象向上转型(对象数据类型转换)
- 把创建的子类对象当作父类看待和使用。
- 子类转换为父类,向上转型。
- 格式:
<父类名称> <对象名称> = new <子类对象>();
对象向下转型(对象数据类型转换)
- 父类转换为子类,向下转型,进行强制类型转换。
- 格式:
<子类名称> <对象名称> = (子类名称)<父类对象>; - 提示:向下转型时需要,先使用
instanceof关键字用来判断某一个对象的类型是否继承自另一个类型,防止向下转型时不是父类的子类,导致转换失败。
面向对象特性 - 多态
- 多态通过向上转型和向下转型,使得我们允许以统一的方式管理不同类型的对象。
- 多态是建立在继承的基础上的。当我们有一个父类和一个或多个子类,并且子类继承了父类,那么多态的情境就产生了。
- 关键的一点是,子类可以重写(覆盖)父类中的方法。这意味着子类可以提供自己的实现版本,当使用这个方法时,会根据实际的对象类型来调用相应的版本。
- 多态的主要特性体现在:当我们使用父类类型的引用指向子类对象时,调用方法时,实际执行的是子类中的方法,而不是父类中的方法。这种能力使得我们能够在不改变代码结构的情况下,通过改变对象的实际类型来改变程序的行为。
- 提高代码的健壮性、灵活性、可维护性。(可以使用统一的接口来处理不同的对象类型,多态也支持开放封闭原则,即对扩展开放,对修改封闭)
要求
- 多态的实现建立在有继承关系上,子类必须继承自父类。
- 父类引用指向子类实例:声明一个父类类型的变量,但让它
new一个子类的对象。
注意事项
- 使用
instanceof关键字可以检查对象是否是特定类的实例。 - 向上转型(将子类对象赋值给父类引用)是自动的,而向下转型(将父类引用赋值给子类引用)需要显式类型转换,并且可能需要使用
instanceof进行检查以避免ClassCastException。
适用场景
- 当需要编写可重用的代码,这些代码能够处理不同类型的对象时,多态非常有用。
案例
// 父类
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
// 子类
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog says: Bow Wow");
}
}
// 子类
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat says: Meow");
}
}
// 测试类
public class PolymorphismExample {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog();
Animal myCat = new Cat();
// 向上转型
Animal[] animals = {myAnimal, myDog, myCat};
// 多态性的体现
for (Animal animal : animals) {
animal.sound(); // 根据实际对象类型调用相应的方法
}
// 向下转型
if (myAnimal instanceof Dog) {
Dog myRealDog = (Dog) myAnimal;
myRealDog.sound();
}
}
}
静态绑定(前期)
静态绑定发生在编译时期,它基于变量的声明类型或者方法签名。以下是一些静态绑定的特点:
- 静态绑定使用
private、static、final修饰的变量或方法是早期绑定。 - 编译器在编译时就能够确定调用哪个方法。
- 静态绑定提高了性能,因为方法调用的解析在编译时就已经完成。
动态绑定(后期)
动态绑定发生在运行时期,它基于对象的实际类型。以下是一些动态绑定的特点:
- 如果被调用的方法在编译期间无法被确定下来,只能够在程序运行期间根据实际的类型绑定相关的方法,这种绑定方式被称为后期绑定。
- 动态绑定允许更灵活的代码,因为可以在运行时改变对象的类型,从而改变方法的实现。
编译时多态
编译时多态通常指的是方法重载:
- 方法重载是编译时多态的一种形式,它允许同一个类中存在多个名称相同但参数列表不同的方法。
- 编译器根据方法签名(方法名称和参数类型)来决定调用哪个重载方法。
运行时多态
运行时多态通常指的是方法重写:
- 方法重写是根据实际的类型决定调用哪个重写的方法,它发生在运行期间,因此被称为运行时多态。
- 当父类的引用指向子类的对象时,调用一个方法时,会调用子类中重写的方法,而不是父类中的方法。
包
- 包(package)是用于组织类和接口的一种机制,它有助于避免命名冲突,并且以逻辑方式组织相关的类和接口。
要求
package语句必须位于 Java 文件的第一个非注释性语句。- 一个 Java 文件只能有一个
package语句。 - 如果一个类加上了
package语句,那么该类的完整类名就是包名加上类名。
注意事项
- 包名通常使用小写字母,多个单词之间使用小数点(
.)分隔。 - 包名应具有描述性,能够清晰地表达其所包含的类的功能或用途。
- 包路径应与源文件的目录结构保持一致。
适用场景
- 解决类名重复产生冲突的问题。
- 便于软件版本的发布。
- 提高代码的组织性和可维护性。
关键字:package、import
格式
// 方案一,导入具体的类
import 包名.类名;
// 方案二,导入该包下的所有类
import 包名.*;
案例
// 文件名为: com/example/myapp/Message.java
package com.example.myapp;
// 定义一个简单的 Message 类
public class Message {
public void displayMessage() {
System.out.println("Hello from the Message class!");
}
}
// 文件名为: Main.java
package com.example;
// 导入 com.example.myapp 包下的 Message 类
import com.example.myapp.Message;
public class Main {
public static void main(String[] args) {
// 创建 Message 类的实例
Message message = new Message();
// 调用 displayMessage 方法
message.displayMessage();
}
}
第四阶段 - 进阶功能(代码优化)
抽象
- 将“类”或“函数”定义为抽象。
- 抽象类的作用类似于“模板”。
- 主要用于继承,有利于程序的扩展。
- 只有方法声明,没有具体方法实现。
- 抽象类中不一定有抽象方法,但抽象方法所在的类一定是抽象类。
- 子类继承抽象父类后,子类必须重写父类中所有的抽象方法,除非子类还是一个抽象类。
- 抽象方法没有具体的代码实现,没有方法体。
- 抽象类中可以有非抽象的方法。
要求
- 以血缘、种类的方式进行继承,只支持继承单个父类。
注意事项
- 只有用
abstract关键字声明的抽象方法,没有具体方法实现,没有花括号{}方法体。 - 抽象方法不能使用
private、finall、static修饰符,因为抽象方法需要能被访问、能被修改、能被继承重写。 - 抽象方法必须被重写实现,才能被使用。
适用场景
- 定义通用属性和方法、强制规范子类行为、处理复杂类结构。
关键字:abstract
格式
<访问权限> abstract class <类名> {
<访问权限> abstract <返回值类型> <方法名>();
}
案例
// 有抽象方法,所在的类必须是抽象类。
public abstract class Test {
// 没有方法体,即没有`{}`
public abstract void eat();
}
接口
- 将“类”定义为接口。
- 微观角度:接口是一种能力和约定,某个事物对外提供的一些功能和声明。
- 宏观角度:接口是一种标准
- 可以利用接口实现多态,同时接口弥补类类的单一继承的弱点。
- 程序的耦合度降低、更自然的使用多态、设计与实现完全分离、更容易搭建程序框架、更容易更换具体实现。
要求
- 以行为、方法、能力的方式,支持继承多个抽象父类。
- 所有属性都是公开静态常量
public static final,隐式默认有添加。 - 所有方法都是公开抽象方法
public abstract,隐式默认有添加。
注意事项
- 接口不能被实例化。
- 可以作为引用类型。 没有构造方法和代码块。
- 接口支持多继承
- 任何类在实现接口时,必须实现接口中所有的抽象方法,除非子类是抽象类。
- 接口的属性:默认使用
public、static、final关键字修饰。 - 接口中的方法:默认使用
public、abstract关键字修饰。 - 接口继承接口,使用
extends继承类的方式继承接口。
适用场景
- 定义规范、实现多态性、隐藏实现细节。
关键字
- 定义接口:
interface - 继承接口:
implements
定义接口
格式
// 定义接口
<访问权限> interface <接口名> {
// 代码块
}
案例
// 定义接口
public interface Test {
// 代码块
}
继承接口
- 一个类可以通过
implements关键字继承一个或多个接口。接口定义了一组抽象方法,实现接口的类必须提供这些方法的实现。
格式
// 继承接口
class <类名> implements <接口名>,<接口名>,... {
// 实现接口中定义的所有抽象方法
}
案例
// 定义一个接口 Drawable
interface Drawable {
void draw();
}
// 定义另一个接口 Resizable
interface Resizable {
void resize(int percentage);
}
// 实现这两个接口的类 Shape
class Shape implements Drawable, Resizable {
// 实现接口 Drawable 的 draw 方法
@Override
public void draw() {
System.out.println("Drawing the shape.");
}
// 实现接口 Resizable 的 resize 方法
@Override
public void resize(int percentage) {
System.out.println("Resizing the shape to " + percentage + "% of its original size.");
}
}
// 主类来测试 Shape 类
public class InterfaceExample {
public static void main(String[] args) {
// 创建 Shape 类的实例
Shape shape = new Shape();
// 调用 draw 方法
shape.draw();
// 调用 resize 方法
shape.resize(50);
}
}
静态
static关键字可以用来修饰类的成员变量(属性)和方法。- 静态成员被称为静态属性(或类属性)和静态方法(或类方法)。
- 静态成员是类的所有实例共享的,无论创建多少个对象,静态成员都只有一份。
- 可以不通过对象实例,直接使用类名来访问静态成员。
要求
用 static 关键字声明静态成员。
注意事项
- 静态方法可以直接访问静态成员,但不能直接访问非静态成员。
- 静态方法中不能使用
this或super关键字,因为这些关键字与对象实例相关。 - 静态方法是继承的,但不能被重写(尽管可以隐藏),并且不参与多态。
- 静态代码块在类被加载到 JVM 时执行,且只执行一次,通常用于初始化静态成员。
适用场景
- 静态变量:适用于定义全局配置、常量、计数器或共享资源。
- 静态方法:适用于与类相关的辅助功能或工具方法,可以直接通过类名调用。
- 静态类:通常用于定义工具类或帮助类,这些类不需要实例化即可使用。
关键字:
static
案例
public class UtilityClass {
// 静态变量
public static final int MAX_COUNT = 100;
private static int counter = 0;
// 静态代码块
static {
System.out.println("Static block initialized.");
// 初始化静态变量
counter = 10;
}
// 静态方法
public static int getCounter() {
return counter;
}
// 静态方法,增加计数器
public static void incrementCounter() {
counter++;
}
// 静态方法,不能直接访问非静态成员
// public static void printNonStaticField() {
// System.out.println(nonStaticField); // 错误,不能直接访问非静态成员
// }
}
public class StaticExample {
public static void main(String[] args) {
// 直接通过类名访问静态变量
System.out.println("Max Count: " + UtilityClass.MAX_COUNT);
// 直接通过类名访问静态方法
System.out.println("Counter before increment: " + UtilityClass.getCounter());
UtilityClass.incrementCounter();
System.out.println("Counter after increment: " + UtilityClass.getCounter());
}
}
最终
- 用于声明变量、函数、类,以表达某种不可变或不可继承的语义。
- 确保了类的某些部分或行为在继承过程中不会被改变,从而维持了原有设计的稳定性和一致性。
要求
final声明变量:属性的值不可改变,在声明时必须被初始化。final声明方法:方法不能被覆盖。final声明类:类不能再被继承。
注意事项
final变量在多线程环境中是线程安全的,因为它们的值不能被更改。final修饰符在声明时必须位于最前面。static和final可以同时修饰变量,表示这是一个静态常量。
适用场景
定义常量、修饰方法、修饰类、修饰方法参数。
关键字:final
案例
public final class FinalClass { // 不能被继承
public final int finalVar = 10; // 不能被修改
public static final int STATIC_FINAL_VAR = 20; // 无需实例化且不能再被修改的常量
public final void finalMethod() { // 不能被继承
// 方法体
}
}
反射
- Java反射机制是在运行状态中。
- 对于任意一个类,都能够知道这个类的所有属性和方法.
- 对于任意一个对象,都能够调用它的任意一个方法和属性。
主要功能
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时获取泛型信息。
- 在运行时调用任意一个对象的成员变量和方法。
- 在运行时处理注解。
- 生成动态代理。
Java反射机制主要涉及以下类:
Class类:反射的核心类,可以获取类的属性、方法等信息。Field类:表示类的成员变量,可以用来获取和设置类之中的字段。Method类:代表类的方法,可以用来获取类中的方法信息或者执行类和对象的方法。Constructor类:代表类的构造方法。包路径:
java.lang.Class
注解
- 注解
Annotation是一种用于修饰代码的元数据,可以提供有关程序代码的信息,但不直接参与程序的执行。 - 元注解是用于定义注解的注解。
作用
- 不是程序,可以对程序做出解释。
- 可以被其他程序(如编译器等)读取。
适用场景
可以附加在package、class、method、field等上面,相当于添加流额外的辅助信息,通过反射机制编程实现对这些元数据的访问。
声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明和注释。
定义注解:注解的定义与接口类似,使用@interface关键字。例如,可以定义一个名为MyAnnotation的注解,包含字符串参数和整数参数。
使用注解:定义了注解后,就可以在代码中使用它了。使用注解的方式有两种:直接在类、方法、变量等上使用,或者通过反射来使用。
包路径:
javax.annotation
格式
- 注解是以
@<注释名>在代码中存在的 - 可以添加一些参数值,
@<注释名>(参数)
案例
元注解
- 元注解用于负责注解其他注解。
- Java定义了4个标准元注解,
@Target注解的使用范围、@Retention注解的生命周期、@Documented注解将包含在javadoc中、@Inherited子类可以继承父类中的该注解
自定义注解
异常处理
- 在程序的运行过程中发生的不正常的事件,导致程序中断程序的运行。
- 允许程序员定义在特定情况下应该采取的操作,从而避免程序因未处理的错误而崩溃。
- 异常也是一种对象,是一种类,是一种数据类型(数据结构),有继承关系。 标准异常类:==Java内置的异常类继承结构图==
要求
- 明确异常类型、尽早抛出异常。
注意事项
- 在
try/catch后面添加finally并非强制性要求。 try代码后不能既没catch也没finally。- 允许有多个
catch。 catch不能独立于try存在。
适用场景
- 文件操作、资源释放、用户输入验证。
关键字:try、catch、finally
格式
try {
// 可能抛出异常的代码
} catch(<异常类型> <异常名称>) {
// 处理异常的代码
...
} finally {
// 释放资源
// 无论是否发生异常,都会执行的代码
}
案例
try {
// 尝试打开并读取一个不存在的文件
FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {
// 处理文件未找到的异常
System.out.println("文件未找到: " + e.getMessage());
} catch (IOException e) {
// 处理其他I/O异常
System.out.println("发生I/O异常: " + e.getMessage());
}
手动抛出异常
- 定义一个方法的时候使用
throws关键字声明的方法表示不处理指定的异常,一旦程序出现指定的异常,将会被抛出交给调用方进行处理。
注意事项
- 选择适当的异常类型
关键字:throw、throws
格式
[访问控制关键字] 返回值类型 方法名(参数列表) [throws 异常类, ...] {
throw new <错误类型对象>(<参数>);
}
案例
public void checkValue(int value) throws IllegalArgumentException {
if (value < 0) {
throw new IllegalArgumentException("值不能小于0");
}
// 其他代码...
}
自定义异常类
- 自定义异常类允许开发者定义程序中特有的错误情况。
- 自定义异常有助于将问题封装起来,使得问题处理代码与正常业务逻辑代码分离,提高代码的可读性和可维护性。
注意事项
- 所有异常类都必须是
Throwable的子类。 - 如果自定义异常类是检查时异常(checked exception),则它应该直接或间接继承自
Exception类。 - 如果自定义异常类是运行时异常(unchecked exception),则它应该直接或间接继承自
RuntimeException类。
格式
// 自定义检查时异常类
public class CustomCheckedException extends Exception {
// 构造器
public CustomCheckedException() {
super(); // 调用父类 Exception 的无参构造器
}
public CustomCheckedException(String message) {
super(message); // 调用父类 Exception 的带参构造器
}
}
// 自定义运行时异常类
public class CustomRuntimeException extends RuntimeException {
// 构造器
public CustomRuntimeException() {
super(); // 调用父类 RuntimeException 的无参构造器
}
public CustomRuntimeException(String message) {
super(message); // 调用父类 RuntimeException 的带参构造器
}
}
案例
public class ExceptionDemo {
public static void main(String[] args) {
try {
// 假设某个方法可能会抛出自定义的检查时异常
throw new CustomCheckedException("发生了一个自定义的检查时异常");
} catch (CustomCheckedException e) {
e.printStackTrace();
}
try {
// 假设某个方法可能会抛出自定义的运行时异常
throw new CustomRuntimeException("发生了一个自定义的运行时异常");
} catch (CustomRuntimeException e) {
e.printStackTrace();
}
}
}
BIO
简要说明
- BIO是Java传统的同步阻塞式I/O模型。
- 每个客户端连接请求都会被分配一个线程去处理,服务器端通过线程阻塞的方式来等待客户端的请求,处理完成后线程销毁。
- 类路径:
java.io
主要功能
- 提供简单的I/O操作接口,易于理解和实现。
- 支持基于流的I/O操作,如文件读写、网络通信等。
- 可以通过线程池来管理线程资源,减少线程创建和销毁的开销。
注意事项
- 每个连接对应一个线程,当并发请求量大时,会导致资源浪费和性能瓶颈。
- 线程在等待I/O操作完成时会阻塞,导致CPU资源利用率不高。
- 需要合理配置线程池的大小,以避免资源耗尽或过度竞争。
适用场景
- 适用于连接数目比较小且固定的架构,因为每个连接都需要一个线程来处理。
- 适用于对实时性要求不高的应用,因为BIO在处理大量并发请求时效率较低。
- 适用于简单的并发量不高的应用,如一些传统的Web应用、小型的文件服务器等。
NIO
简要说明
- Java NIO是一种基于通道(Channel)和缓冲区(Buffer)的I/O操作方式,提供了非阻塞式的I/O操作。
- 允许单个线程管理多个输入/输出通道,从而实现更高效的I/O操作,特别是在处理大量并发连接时。
- 类路径:
java.nio
主要功能
- 非阻塞I/O操作:线程在请求I/O操作时不会阻塞,可以继续执行其他任务。
- 选择器(Selectors):可以监控多个通道的事件(如连接建立、数据到达等),单个线程可以处理多个通道。
- 缓冲区(Buffers):用于存储数据,提供数据读写操作,支持数据的直接访问。
- 通道(Channels):用于读取和写入数据,与传统的流相比,通道可以提供更高效的I/O操作。
注意事项
- NIO编程相对复杂,需要理解通道、缓冲区和选择器的概念。
- 正确使用选择器和缓冲区需要更多的编程技巧和错误处理。
- 需要注意内存管理,避免缓冲区溢出或内存泄漏。
- 在高并发情况下,合理配置线程数和选择器,以优化性能。
适用场景
- 适用于需要处理大量并发连接的应用,如服务器端编程、网络通信等。
- 适用于需要高性能I/O操作的应用,如文件传输、消息队列等。
- 适用于需要低延迟响应的应用,如在线游戏、实时通信系统等。
- 适用于需要同时处理多个网络连接的场景,NIO可以显著减少线程数量,提高资源利用率。
注释
数据双向传输
- Selector(选择器/多路复用器):
- Channel(管道):建立传输数据用的链接
- Buffer(缓冲区):发送数据前的等待区域。
注释
写上的数据是不会被删除,而是被覆盖,通过 “下标限制” 来实现读写。
- capacity:容量
- position:读写指针,当前读取到的位置(索引下标)
- limit:读写位置(索引下标)限制
Channel(管道)
方法
channel.read(<缓冲区>):读取。channel.write(<缓冲区>):写入。channel.close():关闭。
FileChannel
- 文件管道
DatagramChannel
- UDP传输协议
SocketChannel
- TCP传输协议
ServerSocketChannel
- TCP传输协议
Buffer(缓冲区)
ByteBuffer
方法
ByteBuffer.allocate(<数字>):分配空间/容量,实例化缓冲区对象。ByteBuffer.limit():读写位置(索引下标)限制。ByteBuffer.put(<写入的数据>):写入数据。ByteBuffer.get():读取数据。ByteBuffer.flip():切换为读模式。ByteBuffer.clear():切换为写模式。ByteBuffer.compact():把未读完的部分向前压缩,并切换为写模式,即将读完的删除,未读完的保留。ByteBuffer.rewind():重头读取数据,将读写指针设为0的位置。ByteBuffer.mark():标记读写指针(索引下标)的位置。ByteBuffer.reset():将读写指针(索引下标)重置到标记的位置。
案例
import java.nio.ByteBuffer;
public class Test {
public static void main(String[] args) {
// 实例化ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(10);
}
}
- MappedByteBuffer
- DirectByteBuffer
- HeapByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
Selector(选择器/多路复用器)
Selector(选择器/多路复用器)的作用是配合一个线程来管理多个Channel(管道),获取这些Channel上发生的事件,这些Channel工作在非阻塞模式下,不会让线程吊死在一个Channel上,适合连接数多,但流浪低的场景。
AIO
简要说明
- AIO提供了真正的异步I/O操作,意味着在执行I/O操作时,线程可以完全释放,不需要等待I/O操作完成,从而可以更有效地利用系统资源。
主要功能
- 异步I/O操作:线程发起I/O操作后可以立即返回,不需要等待I/O操作完成。
- 完成处理器(CompletionHandler):当I/O操作完成时,会调用完成处理器来处理结果。
- 文件通道(FileChannel)的增强:支持更多的文件操作,如文件锁定和解锁。
- 异步通道(AsynchronousChannel):支持异步读写操作,如AsynchronousSocketChannel和AsynchronousFileChannel。
注意事项
- AIO编程模型比同步I/O更为复杂,需要理解异步操作和完成处理器的概念。
- 异步I/O操作的结果处理通常涉及回调机制,这可能会使得代码的流程控制变得复杂。
- 异步I/O操作可能会因为错误处理不当而导致资源泄露或异常处理困难。
- 在使用AIO时,需要注意JVM的实现和操作系统对异步I/O的支持情况,因为这可能会影响性能。
适用场景
- 适用于需要高并发、低延迟的应用,如高负载的Web服务器、文件服务器等。
- 适用于需要处理大量I/O操作,但又不希望为每个操作都分配一个线程的应用。
- 适用于I/O操作密集型应用,如数据库访问、大规模文件处理等。
- 适用于需要提高应用程序响应性的场景,通过异步I/O减少线程阻塞时间。
多线程
- 并发编程
线程池
阻塞模式下,线程同时只能处理一个socket连接。仅适合短连接场景。
并发包
JDBC
第五阶段 - 进阶语法(代码简化)
获取数组中的某个数值
abcd[<整数>]
可变参数
- 将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法就可以通过可变参数实现。
- 传入的参数是一种不定长的列表。
注意事项
- 可变参数的实参可以为0个或任意多个。
- 可变参数的实参可以为数组。
- 可变参数的本质就是数组。
- 可变参数和普通参数一起放在形参列表中时,必须保证可变参数在最后。
- 形参列表中只能出现一个可变参数。
格式
- 数据类型后面加上3个点
.号。
<访问修饰符> <返回类型> <函数名>(数据类型... 形参名) {
}
代码块
匿名代码块
- 匿名代码块的作用是对象统一初始化。
- 类被实例化之前都会执行这个代码块。
- 格式:代码写在
{}中间。
案例
public class Test {
{
System.out.println("匿名代码块");
}
public static void abc() {
}
}
静态代码块
- 类被实例化时会执行这个代码块,但使用了
static关键字修饰,内存中只存在一个。 - 格式:
static {},代码写在{}中间。
内部类
- 定义在类的内部的类称为内部类。内部类所在的类称为外部类。
编译的规则是类名作为文件名,因此编写代码时需要做到一个文件中只有一个类,且类名与文件名一致。
格式
// 外部类
public class Test {
}
// 内部类
class Test02 {
}
调用内部类的语法格式
<外部类名> <变量名1> = new <外部类>();
<外部类名.内部类名> <变量名2> = <变量名1>.new <内部类名>();
局部内部类
静态内部类
匿名内部类
- 当代码中需要调用一个类中的方法时,却发现需要重写该方法才能满足需求,通过在当前代码中直接实例化对象并重写该方法,调用重写后的方法,一步到位。
- 优点:无需再创建一个类继承另一个类,再重写父类方法,再调用新创建的类中的方法。
格式
// 外部类
public class Test {
// 匿名内部类
new Test02() {
// 匿名内部类方法实现部分
}
}
Lambda表达式
- Lambda 表达式是 Java 8 中引入的一个重要的新特性
- 提供了一种更简洁的方式来表示匿名内部类,特别是那些只包含一个方法的接口(称为函数式接口)。
泛型
语法格式
数据类型 变量 = (参数列表) -> {
// 代码块
};
- 参数列表:一组括号内的参数,可以指定参数类型,也可以不指定(编译器会自动推断)。
- 箭头操作符 (
->):将参数和 Lambda 表达式的主体分隔开。 - 表达式或代码块:可以是单个表达式,也可以是一段代码块,如果是表达式,则返回值就是该表达式的结果;如果是代码块,则必须使用
return语句返回值。
平台环境
动态代理/动态加载
函数式编程、集合并行计算
某些版本更新的语法
某些版本新增的功能
待整理
- 基础操作
- 面向对象编程
- 数据类型
- 枚举、注解
- 异常
- 泛型
- 线程
- IO流(文件操作)
- 网络编程
- 反射
- JDBC和连接池
- JVM
Java SE 高级
- 数据结构、算法
- 设计模式
- 正则表达式
- 数据库
常用类(标准类)



