Java基本概念
cmd命令
- cd 目录:进入目录
- cd ..:回退目录
- cd \:回退到盘符目录
- cd 目录1\目录2:进入多级目录
- start 文件名字:打开文件夹或文件
- 盘符名称+冒号:切换盘符
- dir:查看路径下的内容
- del 文件名:删除文件
- cls:清屏
- exit:退出窗口
- ctrl+c:终止命令
- net user:查看用户
- whoami:查看当前用户
- ipconfig:查看ip地址
- netstat -ano:查看进程id
- shutdown -s:关机
- shutdown -r:关机并重启
- shutdown -s -t 60:定时关机,定时60s
- shutdown -r -t 秒数:一段时间后重启
- write:写字板
- calc:启动计算器
- mspaint:画图板
- notepad:打开记事本
- control:打开控制面板
- winver:检查Windows版本
- regedit:打开注册列表编辑器
Java构成
- Java虚拟机(JVM):是Java程序运行的核心组件,负责解释字节码文件并执行相应的指令。
- Java应用程序接口(API):是Java提供的一系列类库和接口,提供了丰富的函数和工具,方便开发者快速开发且有效地执行各种任务。
- Java语言规范(JLS):是Java语言的规范,描述了Java的语法、语义等相关内容,是Java编译器的基础。
- Java编译器:将Java源代码编译为字节码文件,以便在JVM上运行。
- 开发工具:包括Eclipse、IntelliJ IDEA、NetBeans等等,它们提供了丰富的集成开发环境(IDE),可简化开发人员的工作流程和提升开发效率。
- 服务端组件:包括Java Servlet、Java Server Pages(JSP)等等,它们是Java Web开发中最常用的技术,可用于构建Web服务器应用程序。
- 嵌入式Java:包括J2ME 和 Java Card,用于开发移动设备和智能卡等低功耗嵌入式设备。
JDK:java development kit:java开发工具包,是开发人员所需要安装的环境
JRE:java runtime environment:java运行环境,java程序运行所需要安装的环境
JVM:Java 虚拟机 (JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现 (Windows, Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言 “一次编译,随处可以运行” 的关键所在。
JVM介绍
JVM(Java Virtual Machine,Java虚拟机)是一种能够解释和执行Java字节码的虚拟计算机。JVM提供了一个独立于具体硬件平台和操作系统的运行环境,使得Java程序具有高度可移植性。
JVM由类加载子系统、运行时数据区、执行引擎三部分组成。
- 类加载子系统是Java虚拟机的一个重要组成部分,它负责将编译后的Java类文件加载进内存,在运行时创建对应的Java类。类加载过程主要包括三个阶段:加载、链接、初始化。
- 加载阶段:将指定名称的.class文件读入内存,并为之创建一个Class对象。在Java虚拟机规范中,规定了5种不同的类加载器,它们按照层次关系(即双亲委派模型)组织在一起,分别是:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、系统类加载器(System ClassLoader)、自定义类加载器(Custom ClassLoader)、平台类加载器(Platform ClassLoader)。启动类加载器由C++语言实现,是虚拟机的一部分,负责加载核心类库;其他的类加载器都由Java编写,它们负责加载用户自定义的类。
- 链接阶段:将加载进内存的.class文件进行预处理,分为验证、准备、解析三个步骤。
- 验证:确保被加载的类符合Java虚拟机规范,包括语法正确性检查、符号引用验证等;
- 准备:为被加载的类在方法区中分配内存,并赋初值,以供后续的初始化使用;
- 解析:将常量池中的符号引用替换为直接引用,以便访问内存中的对象。
- 初始化阶段:在类被首次“主动使用”时被执行,主动使用包括以下几种形式:创建类的实例、访问类或接口的静态变量、调用类的静态方法等。初始化阶段负责执行类构造器()方法的代码,主要包括静态变量的赋值和静态代码块的合并,确保类在使用前已经被完全初始化。
除了上述三个阶段,类加载子系统还提供了其他一些功能,如:类的卸载、类的转换、类的预处理等。总之,类加载子系统是Java虚拟机中非常重要的一部分,它为Java程序提供了灵活和可扩展的内存管理机制,也是Java程序实现类隔离、动态扩展等高级特性的重要基础。
- 运行时数据区是Java虚拟机的一个重要组成部分,它包含了Java程序在运行过程中需要用到的各种数据结构,主要包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等五个部分。
- 方法区:存储已被虚拟机加载的类型信息、常量、静态变量等数据。在JDK8之前,方法区也称为“永久代”,但在JDK8中移除了“永久代”的概念,取而代之的是“元数据区”,用于存储类的元数据信息。
- 堆:用于存储Java对象实例,是Java程序运行时内存分配的主要场所。堆是所有线程共享的,它的大小可以通过-Xmx和-Xms两个参数进行控制,其中-Xmx用于设置最大堆内存,Xms用于设置初始堆内存。
- 虚拟机栈:每个线程在创建时都会创建对应的虚拟机栈,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。虚拟机栈的大小可以通过-Xss参数进行控制。
- 本地方法栈:与虚拟机栈类似,但主要为Native方法服务,用于存储Native方法的局部变量表、调用参数、返回值等信息。
- 程序计数器:用于记录当前线程执行的字节码行号,它是线程私有的,因此每个线程都有一个程序计数器。
值得注意的是,堆和方法区可以被所有线程共享,在多线程环境下需要保证它们的线程安全。JVM提供了垃圾回收机制,以便自动管理内存空间,防止出现内存泄露和溢出等问题。
除了上述五个部分,还有一个元空间(Metaspace)用于存储类的元数据信息,元数据信息包括类的结构信息、方法信息、注解信息等,它是运行时数据区在JDK8之后新引入的一部分。元空间的大小可以通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数进行控制。
- 执行引擎:执行引擎是Java虚拟机的核心组成部分,它负责解释执行Java字节码指令,将Java代码转化为机器语言指令,从而实现Java程序的运行。执行引擎主要包括三个部分:解析器、解释器和即时编译器。
- 解析器:将.class文件解析成一系列的抽象指令,形成字节码流。Java虚拟机规范中定义了152个指令,包括加载、存储、算术、逻辑、类型转换、方法调用等指令。解析器可以将这些指令解析成虚拟机内部的数据结构,在执行的过程中进行优化。
- 解释器:对字节码流逐条解释执行,将Java字节码翻译成机器指令执行。解释器的优点在于可以实现跨平台性,但它的执行速度较慢。
- 即时编译器:通过动态编译字节码,生成本地机器指令并缓存起来,以后每次执行相同的代码就可以直接使用编译好的本地机器指令,从而提高运行速度。JIT即时编译器是执行引擎的核心,常见的JIT编译器有C1编译器和C2编译器。
执行引擎在内部实现中,还包括了解释器与编译器的混合使用的技术。Java虚拟机会在执行过程中根据程序的运行情况,逐渐将字节码逐渐编译成本地机器指令,提高程序的运行效率。
执行引擎也是Java动态性的实现基础。Java语言具有动态语言的特性,可以在运行时修改和增强类、对象、方法等。Java虚拟机通过动态生成相应的字节码,使得Java程序可以实现动态加载和卸载类、运行时绑定、反射调用等高级特性。
GC垃圾回收
GC(Garbage Collection)是Java虚拟机内存管理的一项重要功能,它会自动清理不再使用的对象占用的内存空间,从而避免程序出现内存泄漏和内存溢出等问题。GC是Java语言的一大优势,它允许开发人员专注于业务逻辑的实现,而无需关心内存管理的细节。
Java虚拟机的内存分为堆和非堆两部分,其中堆是存放对象实例的地方,而非堆则是存放Java虚拟机和Java程序的类信息、常量、静态变量等数据的地方。GC主要负责管理堆内存的分配和回收,它的主要工作步骤包括标记、清除、整理和复制等。
- 标记:首先,GC会从根对象开始,将所有被根对象引用到的对象标记为“存活”状态,未被标记的对象则被视为“垃圾”。
- 清除:接下来,GC会清除所有未被标记的对象占用的内存空间,回收这些垃圾对象所占用的内存空间。
- 整理(可选操作):如果采用的是标记-清除算法或者标记-整理算法,则在清除垃圾对象之后,GC还会对存活对象进行整理,使它们在堆中的分布尽可能紧凑。
- 复制(可选操作):如果采用的是复制算法,则在标记存活对象之后,GC会将存活对象复制到另外一块未使用的内存区域,然后清除原来的垃圾内存空间,从而实现内存的回收。
GC的具体实现方式有多种,包括标记-清除算法、标记-整理算法、复制算法、分代回收等。目前,大部分Java虚拟机都采用了分代回收算法,即将堆内存分为新生代和老年代两个区域,分别采用不同的回收策略,以提高垃圾回收的效率和性能。
环境变量
Java环境变量是指电脑操作系统中用来识别Java安装目录和设置Java运行环境的一组参数。在Java程序开发和运行时,正确配置Java环境变量非常重要,可以确保Java程序的编译、测试和运行等操作正常进行。
Windows系统中配置Java环境变量的步骤:
- 下载并安装Java:在官网上下载并安装Java的安装程序,在安装过程中选择自己需要的版本和选项
- 找到Java安装路径:默认情况下,Java会被安装在操作系统的某个指定路径下,例如C:\Program Files\Java\jdk-15.0.2
- 设置JAVA_HOME:在Windows系统中,打开“控制面板”->“系统和安全”->“系统”->“高级系统设置”,在“系统属性”窗口中,点击“环境变量”按钮。在“系统变量”栏中,点击“新建”按钮,输入变量名为JAVA_HOME,变量值为Java的安装路径,例如C:\Program Files\Java\jdk-15.0.2
- 设置Path:同样在“系统变量”栏中,找到系统的Path变量,在其变量值中添加“%JAVA_HOME%\bin;”这段内容,注意最后要加分号
- 验证Path是否配置成功:在命令行中输入“java -version”命令,如果能够成功显示Java版本信息,说明Path配置成功
在命令行中编译运行java文件
- 编译:javac HelloWord.java
- 运行:java HelloWord
原码/反码/补码
原码、反码和补码是数字在计算机中存储时的编码方式。三种编码方式:
- 原码:在计算机中,一个二进制数的最高位表示为符号位,0表示正数,1表示负数。例如,+7的原码是00000111,-7的原码是10000111。原码的优点是简单明了,缺点是出现“0”的两个非对称状态
- 反码:反码在原码的基础上,按位取反得到。例如,+7的反码是00000111,-7的反码是11111000。反码的缺点是0有两种表示方法,+0和-0
- 补码:补码是计算机中最常用的一种二进制数表示方法,也是原码和反码的补充。正整数的补码和原码相同,负整数的补码和反码加1相同。例如,+7的补码是00000111,-7的补码是11111001(即反码加1)。补码的优点是没有“0”的两个编码对称的问题,同时还可以使用加法器进行计算,简化了运算器的设计
正数原码、反码、补码相同
负数:
-
反码:符号位不变,数值取反
-
补码:反码加1
补码能多记录一个值-128,计算机中的存储和计算都是以补码的形式进行的
基本数据类型
基本数据类型(primitive data types)指的是在程序设计语言中事先定义好、作为语言一部分的简单数据类型,通常由语言内部提供基本操作,不需要进行额外的计算。在多数编程语言中,基本数据类型包含数字、布尔值和字符三种类型。
Java中的基本数据类型:
- byte:8 位有符号整数,取值范围从-128到127
- short:16 位有符号整数,取值范围从-32768到32767
- int:32 位有符号整数,取值范围从-2147483648到2147483647
- long:64 位有符号整数,取值范围从-9223372036854775808到9223372036854775807
- float:32 位单精度浮点数
- double:64 位双精度浮点数
- char:16 位 Unicode 字符,取值范围从’\u0000’到’\uffff’
- boolean:表示 true 和 false
定义long后加 L,定义float后加 F
这些基本数据类型在 Java 中都是原语类型,也就是说它们不是对象,因此不使用 new 运算符进行实例化,而是直接声明使用。基本数据类型的变量不保存对象的引用,而直接保存数据值,因此不需要进行垃圾回收操作。同时,基本数据类型的变量可以直接使用算术和逻辑运算符进行计算,具有较高的计算效率。
switch表达式
Switch表达式是在Java 12中引入的新特性,它允许开发人员编写简洁、易读的代码来处理多个条件分支。相较于传统的Switch语句,Switch表达式具有以下优点:
- 首先,它消除了需要break语句的需要,并且还提供了lambda表达式类似的“箭头”语法
- 其次,它可以返回一个值,可以直接用于赋值操作和方法调用
- 同时,Switch表达式也避免了一个常见的问题,即忘记写break导致下一个case被无意识地执行的问题
由于Switch表达式是在Java 12中引入的新特性,因此开发人员需要使用-Java 12编译器才能将其编译为字节码并在运行时使用。在Switch表达式中,每个分支都是一个代码块,它可以包含一组语句。与传统Switch语句不同的是,Switch表达式的返回值不需要为void,而是可以是任何类型,包括基本数据类型和对象类型。
Switch表达式的示例:
// 使用 Switch 表达式匹配字符串值
String day = "TUE";
int numLetters = switch (day) {
case "MON", "FRI", "SUN" -> 6;
case "TUE" -> 4;
case "THU", "SAT" -> 5;
case "WED" -> 3;
default -> 0;
};
System.out.println(numLetters); // 输出 4
Switch表达式中可以作为参数的值类型包括:枚举类型、字符串类型、整数类型
Switch表达式不支持浮点数、布尔类型的值
运算符
- 算术运算符
算术运算符用于执行算术运算,包括加、减、乘、除、求余等。
运算符 | 描述 |
---|---|
+ | 相加 |
- | 相减 |
* | 相乘 |
/ | 相除 |
% | 求余数 |
- 关系运算符
关系运算符用于比较两个值之间的关系,结果为布尔类型(true或false)
运算符 | 描述 |
---|---|
== | 判断两个操作数是否相等 |
!= | 判断两个操作数是否不相等 |
> | 判断左操作数是否大于右操作数 |
>= | 判断左操作数是否大于或等于右操作数 |
< | 判断左操作数是否小于右操作数 |
<= | 判断左操作数是否小于或等于右操作数 |
- 逻辑运算符
逻辑运算符用于在条件语句中结合多个条件,并返回布尔类型的结果。
运算符 | 描述 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
- 位运算符
位运算符用于在二进制位级别上执行操作,如按位与、按位或、按位异或等。
运算符 | 描述 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
~ | 按位取反 |
<< | 左移 |
>> | 右移 |
>>> | 无符号右移 |
- 赋值运算符
赋值运算符用于将右侧表达式的值赋给左侧操作数,并返回左侧操作数的值。
运算符 | 描述 |
---|---|
= | 简单赋值 |
+= | 加并赋值 |
-= | 减并赋值 |
*= | 乘并赋值 |
/= | 除并赋值 |
%= | 模并赋值 |
<<= | 左移并赋值 |
>>= | 右移并赋值 |
&= | 按位与并赋值 |
|= | 按位或并赋值 |
^= | 按位异或并赋值 |
- 三元运算符
三元运算符是Java中唯一的一种需要三个操作数的运算符。
result = condition ? value1 : value2
其中,condition
是一个布尔表达式,如果它的值为true
,就返回value1
,否则返回value2
。三元运算符通常用于简化某些条件语句的书写。
- 实例运算符
实例运算符是Java中一种特殊的运算符,它用于检查一个对象是否为某个类的实例。
object instanceof classname
其中,object
是要进行检查的对象,classname
是被检查的类名。如果object
是 classname
的实例,则返回true
,否则返回false
。实例运算符通常用于类型检查和类型转换的场合。
- 类型转换运算符
类型转换运算符用于在不同类型之间进行显式转换。它将一个数据类型转换为另一个数据类型,并且可以将一个数据类型的值赋给另一个数据类型的变量。Java中的类型转换运算符包括以下三种:
- 强制类型转换:将一个大范围的数据类型转换为一个小范围的数据类型,需要使用强制类型转换运算符
(type)
。例如:int a = (int) 3.14;
。 - 自动类型转换:将一个小范围的数据类型自动转换为一个大范围的数据类型,这种类型转换是自动进行的,无需使用任何运算符,byte、short、char类型数据参与运算会先提升为int类型。例如:
int a = 123; double b = a;
。 - 字符串转换:将其他类型的数据转换为字符串类型,需要使用字符串连接运算符
+
。例如:String s = "The value of a is " + a;
。
三大特性
Java的三大特性是封装、继承和多态。这三大特性是Java语言所遵循的面向对象程序设计(Object-Oriented Programming,OOP)的基本原则。
- 封装
封装指的是将类的数据和方法包装起来,对外部实现隐藏,只暴露必要的接口,避免外部程序直接访问对象的内部实现细节。封装的好处在于保证了对象的独立性,提高了程序的安全性和可读性,易于维护和拓展。
- 继承
继承指的是从一个已有的类中派生出一个新的类,并且新的类会完全继承原来类的所有属性和方法,并且可以在此基础上进行扩展。继承的好处在于可以提高代码的重用性和灵活性,避免了重复代码的编写,同时也方便了程序的维护和升级。
- 多态
多态指的是同一种行为具有多种不同表现形式或形态的能力。它是继承和封装的一个自然结果,具体表现为一个对象的多种状态,以及对不同对象的相同操作,可以产生不同的行为结果。多态的好处在于增强了程序的拓展性和可维护性,能够减少代码的冗余,并且提高了程序的可读性和可扩展性。
关键字
权限修饰符:
public>protected>默认>private
Javac:
将java源代码转化成JVM能够识别的语言(Java字节码)
javap:
反编译,javap test.class
static:
- 修饰成员变量,随着类的加载而加载,优先于对象
- 修饰成员方法,不需要创建对象,可以直接通过类名调用,多用于工具类
- 静态代码块,当第一次用到本类时,静态代码块执行唯一的一次,静态的内容优先于非静态执行
- 静态不能直接访问非静态,只能访问静态
this:
- 指向当前对象的引用,区分成员变量和局部变量
- this()只能写在构造方法中,第一条语句
super:
- 在子类的构造方法中调用父类的构造方法
- super()必须为子类构造函数中的第一行
- 可以访问父类的成员方法或变量,super.方法名()
final:
- 修饰类,不能被继承
- 修饰方法,不能被重写
- 修饰变量,值不能变,必须赋初始值
finally:
通常放在 try…catch…的后面,程序无论正常执行还是发生异常,只要 JVM 不关闭都能执行,释放资源
finalize:
在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,是由垃圾收集器在销毁对象时调用的
instanceof:
一个对象是不是另一个类(接口)的实例,或者直接或者间接子类或者实现类
对象四种关系
- 依赖关系
依赖关系是Java中最简单的一种关系,表示一个类的方法调用了另一个类的方法或者使用了另一个类的对象作为参数。依赖关系通常是临时性的、非常脆弱的,因为两个类之间并没有强制性的联系,任何一个类的变化都可能影响到另一个类。
- 关联关系
关联关系表示两个类之间的对象存在某种连接,比如“老师”和“学生”就是一种典型的关联关系。在Java中,关联关系可以通过成员变量来实现,一个类的对象可以包含另一个类的对象作为成员变量。关联关系是一种比较强的、持久性的关系,两个类之间的连接通常是双向的,也可以是单向的。
- 组合关系
组合关系是一种比关联关系更加强的关系,表示一个类的对象包含了另一个类的对象,并且包含对象是不能独立存在的,比如“汽车”和“轮胎”就是一种典型的组合关系。在Java中,组合关系通常通过成员变量来实现,并且子对象的生命周期和父对象相同,父对象删除时子对象也会被删除。
- 继承关系
继承关系是Java中最复杂、最强大的一种关系,表示一个类从另一个类继承了所有的属性和方法。在Java中,继承关系使用extends关键字来实现,子类可以重写父类的方法,也可以添加自己的属性和方法。继承关系是一种非常重要的面向对象编程的思想,它可以提高代码的重用性和可维护性,但也需要注意避免过度使用。
对象的引用
- 强引用
强引用是Java中最基本、最常见的引用类型。如果一个对象具有强引用,那么即使内存紧张,垃圾回收器也不会回收该对象。只有当所有对该对象的强引用都被释放时,垃圾回收器才会将该对象标记为可回收的垃圾对象。
Object obj = new Object();
- 弱引用
弱引用是一种比强引用更弱的引用类型,它不会阻止垃圾回收器回收该对象。如果一个对象只有弱引用,那么在下一次垃圾回收时,无论内存是否充足,都会被自动回收。
在Java中,可以通过java.lang.ref.WeakReference
类来创建弱引用对象,例如:
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
- 软引用
软引用是介于强引用和弱引用之间的一种引用类型,它比弱引用更强,但比强引用更弱。如果一个对象只有软引用,那么当内存紧张时,垃圾回收器会尽可能多地保留该对象,只有当内存不足时才会回收该对象。
在Java中,可以通过java.lang.ref.SoftReference
类来创建软引用对象,例如:
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
- 虚引用
虚引用是Java中最弱的一种引用类型,也被称为“幽灵引用”。虚引用与其它三种引用类型的区别在于,虚引用并不能通过引用来获取到相应的对象,引用的唯一作用就是在该对象被垃圾回收时收到一个系统通知。
在Java中,可以通过java.lang.ref.PhantomReference
类来创建虚引用对象,例如:
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj);
内存模型
栈
Java 程序在运行过程中会使用到线程栈,也称为 JVM 栈,是 Java 虚拟机的内部数据结构。每个线程都有一个私有的栈空间,用来存储该线程执行方法时的局部变量、操作数栈、返回值和异常处理等数据。栈空间大小是预先定义好的,无法改变,当栈空间不足时就会抛出异常。
堆
Java 中的堆是 Java 虚拟机所管理的内存中最大的一块,用来存放对象实例及数组等动态创建的数据。Java 堆是共享的,在虚拟机启动时就已经被创建,大小也可以通过参数配置。
Java 堆中的对象不需要程序员手动回收,而是由垃圾回收器自动回收不再使用的对象,因此程序员只需要关注对象的创建和使用,而无须关注它们何时被回收的问题。
方法区
方法区是 Java 虚拟机用来存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的区域。方法区也是共享的,与堆一样在虚拟机启动时就被创建,大小也可以通过参数进行配置。
在一些 JVM 实现中,方法区与永久代是相关联的。它们的主要区别在于虚拟机是否会对内存进行回收。Java 8 之后,永久代已经被彻底移除,取而代之的是一个称为元空间(Metaspace)的区域。
寄存器
寄存器是位于 CPU 内部的存储器,其访问速度非常快,是极其有限且不能直接被程序员访问的。寄存器可用来存储程序计数器、方法调用的参数和返回值等数据,在方法调用时起到了提高代码执行效率的作用。
接口
Java中的接口(Interface)是一种特殊的抽象类,它只包含常量和方法的声明,没有任何实现部分。接口用于描述类具有哪些方法,但并不提供这些方法的具体实现。实现接口的类必须实现接口中定义的所有方法。
定义接口的语法如下:
interface 接口名 {
// 常量(可省略)
public static final 数据类型 常量名 = 值;
// 抽象方法
public abstract 返回值类型 方法名(参数列表);
}
其中,接口中的方法默认为 public abstract,常量默认为 public static final
Java中接口的主要特点包括:
- 接口不能被实例化,因为它没有实现部分
- 接口可以被类实现,一个类可以实现多个接口
- 接口可以继承一个或多个接口
- 接口中的方法都是公有并且抽象的,不能有实现部分,实现接口的方法必须是公有的
- 接口中的常量必须是 public static final 类型的
使用接口的主要场景包括:
- 在定义对象时,指定其能够响应的方法列表,而不关心具体的实现
- 实现面向对象编程中的多态性,通过一个统一的接口来调用不同的实现
- 通过接口来定义组件之间的协议,实现组件的解耦
抽象类
Java中的抽象类(Abstract Class)是一种特殊的类,它不能被实例化,只能被其他类继承,并且包含至少一个抽象方法。抽象方法是一种没有实现的方法,只包含方法签名和返回类型,抽象方法必须在子类中重写并提供具体实现才能被使用。
定义抽象类的语法如下:
public abstract class 类名 {
// 抽象方法
public abstract 返回类型 方法名(参数列表);
// 具体方法
public void 具体方法名() {
// 具体实现
}
}
其中,抽象方法必须用关键字 abstract 来修饰,而抽象类可以包含抽象方法和非抽象方法。
Java中抽象类的主要特点包括:
- 抽象类不能被实例化,只能被子类继承
- 抽象类可以包含构造方法、静态方法、成员变量、成员方法等,但是它们不能是私有的或者 final 的
- 如果一个类包含抽象方法,那么它必须被声明为抽象类
- 抽象类的子类必须实现抽象类中所有的抽象方法
使用抽象类的主要场景包括:
- 在某些情况下,类的实现需要在子类中完成,但是父类无法确定如何实现,此时可以用抽象方法来定义接口,让子类实现具体的逻辑
- 抽象类也可以用来封装不变部分,允许可变部分延迟到子类中实现,从而扩展类的功能
内部类
Java内部类是一种定义在另一个类内部的类,可以访问外部类的成员变量、方法和构造函数,也可以实现接口和继承其他类。Java内部类分为四种类型:成员内部类、静态内部类、局部内部类和匿名内部类。
- 成员内部类:定义在另一个类的内部,并且不使用static关键字修饰的内部类。成员内部类可以直接访问外部类的成员变量和方法,而外部类不能直接访问其成员内部类的成员。
示例代码:
public class Outer {
private int num = 10;
// 成员内部类
public class Inner {
public void print() {
System.out.println("num = " + num);
}
}
}
- 静态内部类:定义在另一个类内部,并且使用static关键字修饰的内部类。静态内部类无法直接访问外部类的非静态成员变量和方法,只能通过对象来访问。
示例代码:
public class Outer {
private static int num = 10;
// 静态内部类
public static class Inner {
public void print() {
System.out.println("num = " + num);
}
}
}
- 局部内部类:定义在一个方法或作用域内的内部类,只有在该方法或作用域内才有效。局部内部类可以访问外部类的成员变量和方法,但是要求外部类成员变量必须是final类型。
示例代码:
public class Outer {
public void print() {
final int num = 10;
// 局部内部类
class Inner {
public void printNum() {
System.out.println("num = " + num);
}
}
Inner inner = new Inner();
inner.printNum();
}
}
- 匿名内部类:没有类名的内部类,通常用来实现接口或继承父类。匿名内部类可以在创建对象时直接定义和实现。
示例代码:
public class Outer {
public void print() {
// 匿名内部类实现接口
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
方法重载
Java方法重载(Method Overloading)指的是在同一个类中,使用相同的方法名,但参数列表不同的多个方法。Java编译器会根据调用时传入的参数类型和个数,自动选择调用对应的方法。
方法重载的条件:
- 方法名必须相同
- 参数列表必须不同,可以是参数的数量、类型或顺序不同
- 返回值类型可以不同,但不能是仅有返回值类型不同的两个方法
示例代码:
public class Calculator {
public int add(int num1, int num2) {
return num1 + num2;
}
public double add(double num1, double num2) {
return num1 + num2;
}
public int add(int num1, int num2, int num3) {
return num1 + num2 + num3;
}
}
在上面的例子中,我们定义了三个add方法,分别接收两个int型参数、两个double型参数和三个int型参数。当我们调用add方法时,编译器会根据传入的参数类型和个数自动选择调用对应的方法。
Calculator calculator = new Calculator();
int result1 = calculator.add(1, 2); // 调用第一个add方法
double result2 = calculator.add(1.0, 2.0); // 调用第二个add方法
int result3 = calculator.add(1, 2, 3); // 调用第三个add方法
注意:方法重载并不是通过返回值类型来区分方法的。
方法重写
Java方法重写(Method Overriding)指的是子类对父类的方法进行重新实现。在子类中定义一个与父类方法名、返回值类型、参数列表都相同的方法,但实现内容不同,这就是方法重写。重写的方法必须和被重写的方法有相同的访问修饰符。
方法重写有以下几个特点:
- 重写的方法必须和被重写的方法拥有相同的名称、参数列表和返回类型
- 重写的方法不能缩小父类方法的访问权限,但可以扩大
- 重写的方法不能抛出新的或更广泛的异常,但可以抛出比父类方法范围更小的异常或者不抛出异常
- 重写的方法不能使用final修饰符,因为final方法不能被覆盖
示例代码:
public class Animal {
public void move() {
System.out.println("动物可以移动");
}
}
public class Dog extends Animal {
@Override
public void move() {
System.out.println("狗可以跑和走");
}
}
public class Cat extends Animal {
@Override
public void move() {
System.out.println("猫可以跳和爬");
}
}
在上面的例子中,我们定义了三个类:Animal、Dog和Cat。其中,Dog和Cat都继承自Animal,并且重写了Animal中的move方法。当我们调用move方法时,会根据对象的实际类型来选择调用对应的move方法。
Animal animal1 = new Animal();
animal1.move(); // 输出:动物可以移动
Animal animal2 = new Dog();
animal2.move(); // 输出:狗可以跑和走
Animal animal3 = new Cat();
animal3.move(); // 输出:猫可以跳和爬
注意:在进行方法重写时,子类方法中不能使用super关键字调用父类被重写的方法。如果要在子类中调用父类的方法,可以使用super关键字调用父类的其他非被重写方法。
可变参数
Java可变参数(Variable Arguments)指的是一种允许方法接受不定数量参数的特性,也称为varargs。使用可变参数,可以在调用方法时传递数量不确定的参数。Java可变参数是JDK 1.5后新引入的特性。
Java可变参数的定义格式如下:
修饰符 返回值类型 方法名(数据类型... 参数名) {
// 方法体
}
其中,参数名紧跟着三个点号(…)表示这是一个可变参数。可变参数在方法中被当作数组处理,因此可以使用数组相关的操作和语法。
示例代码:
public class Calculator {
public int add(int... nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result1 = calculator.add(1, 2);
int result2 = calculator.add(1, 2, 3, 4);
int result3 = calculator.add(1, 2, 3, 4, 5, 6);
System.out.println(result1); // 输出:3
System.out.println(result2); // 输出:10
System.out.println(result3); // 输出:21
}
}
注意:Java中每个方法最多只能定义一个可变参数列表,并且必须是该方法的最后一个参数。
代码块
Java中的代码块(Code Block)是指一段被花括号({})包含的代码,可以出现在类、方法和构造函数中。根据其位置和声明方式的不同,Java代码块可以分为以下三类:
- 静态代码块(Static Code Block)
静态代码块是在类加载的时候执行且只执行一次的代码块。它用关键字 static
来修饰,定义在类中,方法外。一般用于初始化静态变量或者执行一些只需要在类第一次加载时执行一次的操作。
静态代码块的语法格式如下:
static {
// 静态代码块的代码
}
示例代码:
public class Test {
static {
System.out.println("静态代码块被执行!");
}
public static void main(String[] args) {
System.out.println("主函数被执行!");
}
}
在上面的例子中,我们定义了一个Test类,并在其中定义了一个静态代码块。当程序运行时,先执行静态代码块,再执行main函数。输出结果表明了静态代码块先于主函数被执行。
- 实例代码块(Instance Code Block)
实例代码块是在每次创建对象时都会被执行的代码块,也称为构造代码块。它没有用任何关键字修饰,放在类内部,但不在任何方法内部。一般用于初始化实例变量或者执行一些在创建对象时需要进行的操作。
实例代码块的语法格式如下:
{
// 实例代码块的代码
}
示例代码:
public class Test {
{
System.out.println("实例代码块被执行!");
}
public static void main(String[] args) {
Test test1 = new Test();
Test test2 = new Test();
}
}
在上面的例子中,我们定义了一个Test类,并在其中定义了一个实例代码块。当程序运行时,每创建一个Test对象,就会执行一次实例代码块。输出结果表明了实例代码块在对象创建时被执行。
- 局部代码块(Local Code Block)
局部代码块是指定义在方法或语句中的代码块。它以花括号包括起来,用于限定变量的作用范围。将代码封装到局部代码块中,有助于提高变量的可见性和安全性,减少代码的冗余。
局部代码块的语法格式如下:
{
// 局部代码块的代码
}
示例代码:
public class Test {
public static void main(String[] args) {
int x = 10;
{
int y = 20;
System.out.println(x);
System.out.println(y);
}
// System.out.println(y); // 编译错误,y不在作用范围内
}
}
在上面的例子中,我们定义了一个Test类,并在main函数中定义了一个局部代码块。在局部代码块内,我们定义了一个变量y,输出了变量x和变量y的值。在局部代码块外,y不在作用范围内,因此调用y会导致编译错误。
包装类
Java包装类(Wrapper Class),是指一系列类,它们以对象的形式来表示 Java 的八种基本数据类型(byte, short, int, long, float, double, char, boolean)。这些类位于 java.lang 包下。Java 的八种基本数据类型具有不同的值范围和默认值,但是它们都是值传递,没有引用传递。而Java包装类的出现对于在实际开发中需要和基本数据类型进行交互的情景非常有帮助。
Java包装类提供了一种将基本数据类型转换为对象的方法,可以用于在面向对象的设计中使用基本数据类型,从而让基本数据类型也具有对象的特性。除此之外,Java包装类还为基本数据类型提供了很多有用的方法,例如将字符串转换为基本数据类型信息,或者将字符串表示成基本类型等。
Java包装类主要有以下8个类:
基本数据类型 | 对应的包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
每个包装类都提供了很多方法,以便使用基本数据类型。例如Integer类提供了parseXXX、valueOf和toString等方法,可以将字符串转换为数字,将数字转换为字符串,比较两个数字等操作。
Java包装类的作用主要包括:
- 将基本数据类型转换为对象,实现值引用。例如,通过Integer封装int,可以在方法中返回一个Integer对象,而不需要使用原始的int类型。
- 提供了特定的方法,方便我们对基本数据类型进行操作。例如,Integer类提供了很多方法来进行数学计算、字符串转换等操作。
- 能够将基本数据类型表示为对象,从而使得Java API能够更好地处理这些数据类型。例如,在Java集合框架中只能存储对象,因此如果需要将int类型的数据存储在一个集合中,就需要将其封装成一个Integer对象。
转String
在Java中,有多种方法可以将其他类型的数据转换为字符串类型。
- 使用 toString() 方法
toString() 方法是 Object 类中的一个方法,所有的 Java 对象都继承了它。这个方法可以将一个对象转换成字符串,并返回字符串表示。基本类型的包装类也重写了 toString() 方法,用于将其对应的基本类型数据转换成字符串。
示例代码:
int num = 100;
String str = Integer.toString(num); // 将int类型的数据转换为字符串
System.out.println(str);
- 使用 String.valueOf() 方法
String.valueOf() 是一个静态方法,可以将任何数据类型转换为字符串,包括基本数据类型和引用数据类型。
示例代码:
int num = 100;
String str = String.valueOf(num); // 将int类型的数据转换为字符串
System.out.println(str);
- 使用字符串连接符 “+”
在 Java 中,使用字符串连接符 “+” 可以将其他类型的数据与空字符串(或其他字符串)连接,从而将其转换为字符串类型。
示例代码:
int num = 100;
String str = "" + num; // 将int类型的数据转换为字符串
System.out.println(str);
- 使用 String.format() 方法
String.format() 方法可以将任何类型的数据格式化为字符串类型,并返回结果字符串。
示例代码:
int num = 100;
String str = String.format("%d", num); // 将int类型的数据转换为字符串
System.out.println(str);
- 使用 StringBuilder 或 StringBuffer
StringBuilder 和 StringBuffer 是 Java 中表示可变字符串的两个类。它们提供了 append() 方法,可以将任何类型的数据追加到字符串中,在最后将整个字符串作为结果返回。
示例代码:
int num = 100;
StringBuilder sb = new StringBuilder();
sb.append(num); // 将int类型的数据转换为字符串
String str = sb.toString();
System.out.println(str);
compareTo
Java中的 compareTo() 方法是Java.lang.Comparable接口定义的方法,用于比较当前对象与指定对象的顺序。该方法主要用于实现排序功能,通常在一些容器类中(例如 TreeSet、TreeMap)中被使用。
该方法的语法为:
public int compareTo(T obj)
其中,T表示需要比较的对象类型,obj表示另一个需要比较的对象,返回值为int类型,表示当前对象与参数对象之间的大小关系,其规定如下:
- 当前对象小于参数对象时,返回一个负整数
- 当前对象等于参数对象时,返回0
- 当前对象大于参数对象时,返回一个正整数
注意,如果需要自定义对象进行比较,则需要实现 Comparable
接口,并重写 compareTo()
方法。
下面是一个示例代码,演示如何使用 compareTo() 方法比较两个字符串的大小关系:
String str1 = "hello";
String str2 = "world";
int result = str1.compareTo(str2);
if (result < 0) {
System.out.println(str1 + " is smaller than " + str2);
}
else if (result == 0) {
System.out.println(str1 + " is equal to " + str2);
}
else {
System.out.println(str1 + " is larger than " + str2);
}
输出结果为:hello is smaller than world
compareTo() 方法是Java中用于比较对象大小关系的一个方法,可以根据实际业务需求对其进行灵活使用。
字符串
- String 类
String 类是不可变类,即一旦创建了一个 String 对象后,就不能再修改该对象。因此,如果需要对字符串做频繁的修改操作,每一次修改都会创建一个新的 String 对象,这样会产生大量的临时对象,导致性能低下。
- StringBuilder 类
StringBuilder 类和 StringBuffer 类都是可变类,即可以对字符串进行修改操作,但 StringBuilder 类是线程不安全的。
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("world");
String result = sb.toString();
System.out.println(result);
输出结果为:Hello world
- StringBuffer 类
StringBuffer 类和 StringBuilder 类功能基本相同,但 StringBuffer 类是线程安全的,在需要多线程并发修改字符串的情况下使用 。
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" ");
sb.append("world");
String result = sb.toString();
System.out.println(result);
输出结果为:Hello world
- StringJoiner 类
StringJoiner 类是Java8中新增的类,用于将一组字符串拼接成一个字符串,底层是StringBuilder,线程不安全。
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("foo")
.add("bar")
.add("baz");
String result = sj.toString();
System.out.println(result);
输出结果为:[foo, bar, baz]
Java基础知识
常用API
Java常用API指的是Java语言中提供的标准类库,也称为Java SE API。它包含了丰富的类和接口,提供了处理字符串、文件操作、网络通信、集合框架、多线程、GUI等方面的功能。
- java.lang包:提供Java编程语言的基础类,如Object、String、Math等。这个包中的类不需要显式导入,自动加载到程序中。
- Object类
- equals()方法用于判断两个对象是否相等
- hashCode()方法用于获取对象的哈希码
- toString()方法用于返回对象的字符串表示
- String类
- equals()方法用于判断两个字符串是否相等
- length()方法用于获取字符串的长度
- concat()方法用于拼接两个字符串
- replace()方法用于替换字符串中的某些字符
- Math类
- abs()方法用于求绝对值
- sqrt()方法用于求平方根
- pow()方法用于求幂次方
- random()方法用于生成0到1之间的随机数
- ceil/floor/round()方法用于向上/向下/四舍五入
- Object类
- java.util包:提供一些实用的工具类,如日期和时间类、集合类、随机数生成器、位集合等。
- ArrayList类
- add()方法用于向列表添加元素
- remove()方法用于从列表中删除元素
- size()方法用于获取列表的大小
- get()方法用于获取特定位置的元素
- HashMap类
- put()方法用于添加键值对
- remove()方法用于删除键值对
- get()方法用于获取指定键对应的值
- containsKey()方法用于判断键是否存在
- Date类
- after()方法和before()方法用于比较日期先后
- getTime()方法用于获取距1970年1月1日00:00:00的毫秒数
- ArrayList类
- java.io包:提供输入输出流相关的类,包括字节流和字符流,常用的有File、InputStream、OutputStream、Reader、Writer等。
- File类
- exists()方法用于判断文件/目录是否存在
- isDirectory()方法用于判断是否为目录
- listFiles()方法用于列出目录下的全部文件
- FileInputStream类和FileOutputStream类
- read()方法和write()方法用于读取和写入字节
- close()方法用于关闭输入输出流
- BufferedReader类和BufferedWriter类
- readLine()方法和write()方法用于读取和写入文本行
- newLine()方法用于插入行分隔符
- close()方法用于关闭输入输出流
- File类
- java.net包:提供网络编程相关的类,如Socket、ServerSocket、URLConnection等。
- Socket类
- 构造函数Socket(String host, int port)用于创建一个连接到指定主机和端口号的Socket对象
- getInputStream()方法和getOutputStream()方法用于获取输入输出流对象
- ServerSocket类
- 构造函数ServerSocket(int port)用于创建一个绑定到指定端口号的ServerSocket对象
- accept()方法用于监听客户端的连接请求
- Socket类
- java.awt包和javax.swing包:提供了图形用户界面(GUI)开发所需的类,如窗口、按钮、标签等。
- JFrame类
- setSize()方法用于设置窗体的大小
- setVisible()方法用于显示窗体
- setLayout()方法用于设置布局管理器
- JButton类
- addActionListener()方法用于为按钮添加事件监听器
- setEnabled()方法用于启用/禁用按钮
- JLabel类
- setText()方法用于设置标签显示的文本
- setIcon()方法用于设置标签显示的图像等等。
- JFrame类
- java.sql包:提供访问关系型数据库的类和接口,如Connection、Statement、ResultSet等。
- Connection类
- createStatement()方法用于创建Statement对象
- prepareStatement()方法用于创建PreparedStatement对象
- Statement类和PreparedStatement类
- executeQuery()方法用于执行查询操作
- executeUpdate()方法用于执行更新操作
- close()方法用于关闭Statement对象
- ResultSet类
- next()方法用于移动游标到下一行
- getString()方法和getInt()方法用于获取指定列的字符串和整数值
- Connection类
拷贝/克隆
浅拷贝和深拷贝是Java中对于对象复制的两种不同方式。
浅拷贝
将一个对象复制到另一个对象时,只会复制对象本身及其所有基本数据类型的属性,而不会复制对象包含的引用类型属性所指向的对象。也就是说,浅拷贝产生的新对象和原对象共享同一个引用类型的属性。如果改变了一个对象的引用类型属性,那么另一个对象的相应属性也会发生变化。实现浅拷贝的方式通常是通过Object类的clone()方法实现,或者通过复制构造函数实现。
class Student implements Cloneable {
private String name;
private int age;
private Address address;
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address {
private String city;
public Address(String city) {
this.city = city;
}
}
// 浅拷贝示例:
Student s1 = new Student("Tom", 20, new Address("Beijing"));
Student s2 = (Student) s1.clone();
System.out.println(s1.getAddress() == s2.getAddress());
// true,s1和s2的address属性引用相同的对象
深拷贝
将一个对象复制到另一个对象时,不仅会复制对象本身及其所有基本数据类型的属性,而且会递归复制对象包含的所有引用类型属性所指向的新对象。也就是说,深拷贝产生的新对象和原对象不共享任何引用类型的属性。实现深拷贝的方式较为复杂,可以通过序列化和反序列化实现,也可以通过手动递归复制对象的每个属性实现。
class Student implements Serializable {
private String name;
private int age;
private Address address;
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Student deepCopy() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Student newStudent = (Student) ois.readObject();
return newStudent;
}
}
class Address implements Serializable {
private String city;
public Address(String city) {
this.city = city;
}
}
// 深拷贝示例:
Student s1 = new Student("Tom", 20, new Address("Beijing"));
Student s2 = s1.deepCopy();
System.out.println(s1.getAddress() == s2.getAddress());
// false,s1和s2的address属性引用不同的对象
BigDecimal
BigDecimal类是Java中处理任意精度数字的类,可以用来解决浮点数计算精度缺失问题。
在Java中,基本数据类型中的double和float类型是浮点型,它们只能表示有限的小数。而BigDecimal类则可以表示包括无限小数在内的高精度数字。BigDecimal类提供了一系列算术操作方法,包括加、减、乘、除、取模等。此外,BigDecimal还支持设置精度、舍入模式等操作。
BigDecimal类的构造函数有多种方式:
BigDecimal bd1 = new BigDecimal("3.14159265358979323846"); // 使用字符串来创建BigDecimal对象
BigDecimal bd2 = new BigDecimal(123456789); // 使用long类型来创建BigDecimal对象
BigDecimal bd3 = new BigDecimal(2.71828); // 使用double类型来创建BigDecimal对象
需要注意的是,如果使用double类型来创建BigDecimal对象,可能会出现精度错误。因为double类型的精度只有15位左右,而BigDecimal类默认的精度是无限的,因此需要使用BigDecimal的valueOf()方法来进行转换:
BigDecimal bd4 = BigDecimal.valueOf(2.71828); // 推荐使用valueOf()方法避免精度错误
BigDecimal类的常用方法及其作用如下:
- add(BigDecimal other):加法操作
- subtract(BigDecimal other):减法操作
- multiply(BigDecimal other):乘法操作
- divide(BigDecimal other, int scale, RoundingMode roundingMode):除法操作(scale表示精度,roundingMode表示舍入模式,四舍五入:HALF_UP)
- remainder(BigDecimal other):取模操作
- compareTo(BigDecimal other):比较大小
- equals(Object obj):比较是否相等
- toString():将BigDecimal对象转为字符串表示形式
下面是使用BigDecimal计算圆周率 π 的示例:
public class TestBigDecimal {
public static void main(String[] args) {
BigDecimal pi = new BigDecimal("0"); // 初始化pi值
BigDecimal divisor = new BigDecimal("1"); // 初始化被除数
int i = 1;
while (i <= 1000) {
// 计算1000项级数
if (i % 2 == 1) {
pi = pi.add(new BigDecimal("1").divide(divisor, 1000, RoundingMode.HALF_UP));
} else {
pi = pi.subtract(new BigDecimal("1").divide(divisor, 1000, RoundingMode.HALF_UP));
}
divisor = divisor.add(new BigDecimal("2"));
i++;
}
pi = pi.multiply(new BigDecimal("4")); // 计算π
System.out.println(pi.toString());
}
}
正则表达式
正则表达式(Regular Expression)是一种用于描述字符串模式的语言。在计算机科学中,被广泛应用于字符串匹配、搜索、替换等操作。正则表达式具有简洁、高效、可复用的特点,可以将复杂的字符串操作简化为一行代码。
Java中使用java.util.regex包提供了对正则表达式的支持。该包中提供了Pattern和Matcher两个类,分别用于表示正则表达式和匹配器。下面是一些常用的正则表达式元字符和其含义:
使用正则表达式进行匹配的过程,可以分为编译和匹配两个步骤。编译是将正则表达式字符串转换为Pattern对象的过程,而匹配是根据Pattern对象进行字符串的匹配操作。下面是一个示例,演示了如何使用正则表达式进行字符串匹配:
import java.util.regex.*;
public class TestRegex {
public static void main(String[] args) {
String regex = "\\d+";
String input = "hello 123 world 456!";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
以上代码使用正则表达式 “\d+” 匹配了字符串中的所有数字,并将其输出到控制台。在代码中,先使用Pattern.compile()方法将正则表达式字符串编译成Pattern对象,然后使用Matcher对象进行匹配操作。最终,使用Matcher.find()方法查找匹配,使用Matcher.group()方法提取匹配结果。
在正则表达式中,使用括号来表示分组,括号内包含了需要进行分组的表达式。下面是一些示例:
- (abc)+ :表示将 “abc” 作为一个整体,重复1次或多次。
- (xy|yz) :表示将 “xy” 或 “yz” 作为一个整体进行匹配。
- (ab)*c :表示将 “ab” 重复0次或多次,再加上一个 “c” 进行匹配。
在分组中,还有一个非常有用的功能——捕获分组。捕获分组可以将分组匹配结果进行保存,并可以在后续操作中进行调用。在分组开头加上“(?”和“:”就可以实现非捕获分组和捕获分组。其中,使用“(?”表示非捕获分组,使用“( )”表示捕获分组。下面是一个示例:
import java.util.regex.*;
public class TestRegex {
public static void main(String[] args) {
String regex = "(\\d{4})-(\\d{2})-(\\d{2})";
String input = "2023-04-27";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
System.out.println("Year: " + matcher.group(1));
System.out.println("Month: " + matcher.group(2));
System.out.println("Day: " + matcher.group(3));
}
}
}
以上代码使用正则表达式 “(\d{4})-(\d{2})-(\d{2})” 匹配了一个日期字符串,并将年、月、日三个部分进行了捕获。在代码中,使用Matcher.group(n)方法可以获取匹配结果中的第n个捕获分组。
时间相关类
SimpleDateFormat
SimpleDateFormat
是Java中的一个日期格式化类,它可以将日期对象格式化为字符串,也可以将字符串解析为日期对象。在实际开发中,常常需要对日期进行格式化和解析,SimpleDateFormat
就是用来完成这些任务的。
下面是SimpleDateFormat
的用法示例:
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSimpleDateFormat {
public static void main(String[] args) {
// 创建一个SimpleDateFormat对象,指定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 将日期对象格式化为字符串
Date date = new Date();
String strDate = sdf.format(date);
System.out.println(strDate);
// 将字符串解析为日期对象
try {
Date parsedDate = sdf.parse("2023-04-27 02:28:46");
System.out.println(parsedDate);
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上代码通过SimpleDateFormat
将当前时间格式化成了"yyyy-MM-dd HH:mm:ss"的字符串,并将该字符串解析成了一个Date
对象。
在使用SimpleDateFormat
时,需要注意的是,日期格式字符串中的各个字符都有特殊含义,例如:
- y:表示年份,例如"yyyy"表示4位数字显示
- M:表示月份,例如"MM"表示2位数字显示
- d:表示日期,例如"dd"表示2位数字显示
- H:表示小时(24小时制),例如"HH"表示2位数字显示
- m:表示分钟,例如"mm"表示2位数字显示
- s:表示秒钟,例如"ss"表示2位数字显示
在使用格式化字符串时需要根据需求进行相应的设置,并且需要注意大小写区分。
Calendar
Calendar
是Java中用于处理日期和时间的类,它提供了一系列方法来操作日期和时间,例如获取年、月、日、时、分、秒等信息,以及加减年、月、日等时间单位,实现日期和时间的计算和比较。它还可以将日期格式化为字符串,以及将字符串解析为日期。
下面是Calendar
的用法示例:
import java.util.Calendar;
public class TestCalendar {
public static void main(String[] args) {
// 获取当前时间
Calendar now = Calendar.getInstance();
// 获取年、月、日、时、分、秒
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH) + 1;
int day = now.get(Calendar.DAY_OF_MONTH);
int hour = now.get(Calendar.HOUR_OF_DAY);
int minute = now.get(Calendar.MINUTE);
int second = now.get(Calendar.SECOND);
System.out.printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);
// 将时间后推一天
now.add(Calendar.DAY_OF_MONTH, 1);
int newYear = now.get(Calendar.YEAR);
int newMonth = now.get(Calendar.MONTH) + 1;
int newDay = now.get(Calendar.DAY_OF_MONTH);
System.out.printf("%d-%02d-%02d\n", newYear, newMonth, newDay);
}
}
以上代码演示了如何使用Calendar
获取当前时间,并且将时间后推一天。
在使用Calendar
时,需要注意以下几点:
Calendar.getInstance()
方法可以获取一个Calendar
对象Calendar
中的月份是从0开始计数的,因此需要加1才能得到正确的月份- 通过
add
方法可以对日期进行加减操作,其中第一个参数为时间单位,例如Calendar.DAY_OF_MONTH
表示天数,第二个参数表示要加减的值 Calendar
还提供了众多方法来获取当前时间的各种信息,例如get(Calendar.YEAR)
获取年份
Java核心内容
数组
Java数组是一组相同类型数据的集合,它可以存储基本数据类型或对象类型,并且可以通过下标访问数组中的元素。在Java中,数组是一个对象,它继承自Object
类,并实现了Serializable
和Cloneable
接口。
Java数组的定义方式如下:
// 声明一个整型数组
int[] nums;
// 声明并初始化一个整型数组
int[] nums = {
1, 2, 3};
// 声明并初始化一个整型数组(定义数组长度)
int[] nums = new int[3];
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
上面的代码定义了一个名为nums
的整型数组,分别使用了不同的方式进行声明和初始化,其中第三种方式需要指定数组长度。
Java数组的常见操作包括:
- 访问数组元素:可以通过下标访问数组中的元素,例如
nums[0]
表示访问数组nums
中的第一个元素。 - 遍历数组:可以使用循环来遍历数组中的所有元素,例如:
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
- 数组复制:可以将一个数组的值复制到另一个数组中,例如使用
System.arraycopy()
方法进行数组复制:
int[] nums1 = {
1, 2, 3};
int[] nums2 = new int[3];
System.arraycopy(nums1, 0, nums2, 0, nums1.length);
- 数组排序:可以使用
Arrays.sort()
方法对数组进行排序,例如:
int[] nums = {
3, 1, 2};
Arrays.sort(nums);
异常
Java异常(Exception)是在程序执行过程中发生错误或异常情况时抛出的一种信号,它可以帮助程序员更好地处理程序中出现的错误情况。Java异常分为两类,一类是运行时异常(RuntimeException),另一类是非运行时异常(Checked Exception)
Java异常的处理方式主要有两种,一种是捕获异常并进行处理,另一种是抛出异常并交给上层调用者处理。常见的异常处理语句包括try-catch
和throw
语句。
下面是一个简单的示例,演示了如何使用try-catch
语句捕获异常:
public class TestException {
public static void main(String[] args) {
try {
int a = Integer.parseInt("123");
int b = Integer.parseInt("456");
int c = b / 0;
System.out.println(a + b + c);
} catch (NumberFormatException e) {
System.out.println("数字格式错误");
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
}
}
}
上述代码中,我们尝试将字符串转换为整型,并且进行加法和除法运算,但是由于字符串格式错误和除数为0的异常情况,程序会抛出NumberFormatException
和ArithmeticException
两种异常。通过catch
语句捕获这些异常,我们可以在出现异常时手动指定处理方法,以便程序更好地处理错误情况。
除了try-catch
语句,Java还提供了finally
语句用于在程序执行结束时进行资源释放等清理操作。例如:
public class TestFinally {
public static void main(String[] args) {
try {
System.out.println("start");
int a = 1 / 0;
System.out.println("end");
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("finally");
}
}
}
处理异常
JVM默认处理异常的方式:
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
- 程序停止执行,异常下面的代码不会再执行了
try - catch 方式处理异常:可以让程序继续往下执行,不会停止
throws异常处理
Throwable方法
- public String getMessage():返回此throwable的详细消息字符串
- public String toString():返回此可抛出的简短描述
- public void printStackTrace():把异常的错误信息输出在控制台
自定义异常
- 定义异常类
- 写继承关系
- 空参、带参构造
自定义异常类一般都是以Exception结尾,必须继承:Exception (编译异常) 或 RuntimeException (运行异常)
Throw 和 throws
集合
Collection (单列)
遍历方式
迭代器遍历:不依赖索引,删除元素时使用
- 遍历结束指针不会复位
- 循环中只能用一次next方法
- 不能用集合的方法进行增加或删除
增强for循环:底层是迭代器,修改增强for中的变量,不会改变集合原数据
Lambda表达式遍历:default void forEach(Consumer<? super T> action);
常用方法
List(有序 可重复 有索引)
遍历方式:
迭代器、列表迭代器、增强for、Lambda表达式、普通for
-
列表迭代器:ListIterator
-
迭代器:删除
-
列表迭代器:添加
-
增强for、Lambda表达式:遍历
-
普通for:遍历时操作索引
Vector
- 和ArrayList几乎是完全相同,线程安全。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍
ArrayList、LinkedList
- 线程不安全,相对线程安全的Vector,执行效率高,
- ArrayList是动态数组的数据结构,LinkedList是双向链表的数据结构
- 对于查询操作ArrayList优于LinkedList,因为LinkedList要移动指针
- 对于新增和删除操作LinkedList比较占优势,因为ArrayList要移动数据
ArrayList集合底层原理:
- 利用空参创建的集合,在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
Set(无序 不重复 无索引)
HashSet
无序、可以是 null、线程不安全
底层是哈希表
- JDK8之前:数组+链表
- JDK8开始:数组+链表+红黑树
底层原理:
哈希值的特点:
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
LinkedHashSet
- 有序,使用双向链表维护元素的次序
TreeSet
排序方式
- 自然/默认排序:该对象的类必须实现Comparable接口,重写compareTo(Object obj) 方法
- 定制/比较器排序:实现Comparator接口,重写compare
线程不安全、底层使用红黑树结构存储数据:
自平衡的二叉搜索树
1.每个结点是红的或者黑的
2 根结点是黑的
3 每个叶子结点是黑的(NULL)
4 树中不存在两个相邻的红色结点(即红色结点的父结点和孩子结点均不能是红色)
5 从任意一个结点 (包括根结点)到其任何后代 NULL 结点 (默认是黑色的)的每条路径都具有相同数量的黑色结点
加入红黑树的原因,也是为了避免链表过长,降低了效率。
那为何不直接用红黑树呢?还得加上链表?因为红黑树需要进行左旋,右旋操作, 而单链表不需要。
平衡:为了达到平衡,需要对树进行旋转。而红黑树能够达到自平衡,靠的也就是左旋、右旋和变色。
Map (双列)
遍历方式
键找值:
键值对:
Lambda表达式:
常用方法
put方法:添加/覆盖
Hashtable
-
子类 Properties:key 和 value 都是字符串类型
-
线程安全,不允许使用 null 作为 key 和 value
HashMap
-
允许使用null键和null值;线程不安全
-
子类 LinkedHashMap:有序,使用了一对双向链表来记录添加元素的顺序
TreeMap
-
线程不安全,可自然排序、定制排序
-
底层使用红黑树结构存储数据
ConcurrentHashmap
- 线程安全,多线程场景下使用
Collections
不可变集合
集合不能添加、删除、修改
-
List:直接用
-
Set:元素不能重复
-
Map:元素不能重复,键值对数量最多10个,超过10个用ofEntries
File类
绝对/相对路径
构造方法
判断、获取
public String getParent():返回给定文件对象的父对象
创建、删除
delete删除不走回收站、delete只能删除空文件夹
获取并遍历
public File[] listFiles():获取当前该路径下的所有内容
IO流
分类
按照数据的流向
- 输入流:读数据
- 输出流:写数据
按照数据类型来分
- 字节流 (记事本打开我们不能读懂):字节输入流,字节输出流
- 字符流 (记事本打开我们能读懂):字符输入流,字符输出流
使用场景
如果操作的是纯文本文件,优先使用字符流
如果操作的是图片、视频、音频等二进制文件。使用字节流
如果不确定文件类型,优先使用字节流。字节流是万能的流
随用随创建,什么时候不用什么时候关闭
加密解密:异或一个数字两次会得到原结果
例如:
- 100^10 = 110
- 110^10 = 100
体系结构
字节流 (8位字节)
FileOutputStream
文件字节输出流 (写)
实现步骤:
- 创建字节输出流对象 (调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法,刷新数据
- 释放资源 (关闭此文件输出流 释放与此流相关联的任何系统资源)
实现换行:
- windows:\r\n
- linux:\n
- mac:\r
追加写入:public FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
FileInputStream
文件字节输入流 (读)
实现步骤:
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源 (先开的流最后关闭)
int by;
while ((by=fis.read())!=-1){
System.out.print((char)by);
}
public int read(byte[] b):从输入流读取最多b.length个字节的数据;返回的是读入缓冲区的总字节数,也就是实际的读取字节个数
byte[] bys = new byte[1024]; //1024及其整数倍
int len;
while ((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
异常处理
为了避免出现异常,要使用finally关闭流,并且要判空!!
字符流 (16位字节)
字节流操作中文不是特别的方便,所以Java就提供字符流。
字符流 = 字节流 + 字符集
编码规则
- 在计算机中,任意数据都是以二进制的形式来存储的
- 计算机中最小的存储单元是一个字节
- ASCII字符集中,一个英文占一个字节
- 简体中文版Windows,默认使用GBK字符集
- GBK字符集完全兼容ASCII字符集
- 一个英文占一个字节,二进制第一位是0
- 一个中文占两个字节,二进制高位字节的第一位是1
Unicode字符集的UTF-8编码格式
- 一个英文占一个字节,二进制第一位是0,转成十进制是正数
- 一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数
乱码原因
- 读取数据时未读完整个汉字
- 编码和解码时的方式不统一
解决乱码
- 不用字节流读取文本文件
- 编码解码时使用同一个码表,同一个编码方式
编码解码
IDEA默认使用 UTF-8编码方式
- 一个英文一个字节
- 一个中文三个字节
eclipse默认使用 GBK编码方式
- 一个英文一个字节
- 一个中文两个字节
public byte[] getBytes():使用默认方式进行编码
public byte[] getBytes(String charsetName):使用指定方式进行编码
String(byte[] bytes):使用默认方式进行解码
String(byte[] bytes, String charsetName):使用默认方式进行解码
FileReader
字符输入流 (读)
- public FileReader(File file):创建字符输入流关联本地文件
- public FileReader(String pathname):创建字符输入流关联本地文件
public int read():读取数据,读到末尾返回-1
public int read(char[] buffer):读取多个数据,读到末尾返回-1
注意
- 按字节进行读取,遇到中文一次读多个字节,读取后解码返回一个整数
- 读到文件末尾了,read方法返回-1
原理解析
FileWriter
字符输出流 (写)
-
public void flush():将缓冲区中的数据刷新到本地文件,还可以继续往文件写数据
-
public void close():释放资源/关流,无法往文件中写出数据
缓冲流
在Java中,缓冲流是一种高级的IO流,可以提高IO操作的性能。缓冲流内部使用了一个缓冲区,将数据缓存到内存中,然后再一次性读取或写入,从而减少实际的IO操作次数。与不使用缓冲流相比,使用缓冲流可以大大提高程序的IO操作效率,特别是在处理大量数据时更加明显。
Java中提供了两种缓冲流:缓冲输入流(BufferedInputStream)和缓冲输出流(BufferedOutputStream)。缓冲输入流可以加快读取数据的速度,而缓冲输出流可以加快写入数据的速度。
字节缓冲流
使用缓冲输入流读取文件:
try (InputStream in = new BufferedInputStream(new FileInputStream("file.txt"))) {
int data;
while ((data = in.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
使用缓冲输出流写入文件:
try (OutputStream out = new BufferedOutputStream(new FileOutputStream("file.txt"))) {
String data = "Hello, world!";
out.write(data.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
字符缓冲流
BufferedReader:字符缓冲输入流
public String readLine():读取一行数据
String line;
while((( line = br.readLine()) != null)){
System.out.println(line);
}
字符缓冲输出流
public void newLine():跨平台的换行
转换流
Java转换流(InputStreamReader和OutputStreamWriter)是字符流和字节流之间的桥梁,它们可以将字节流转换为字符流或者将字符流转换为字节流。Java的字符流本身是建立在字节流的基础上的,因此在Java中,字符流是不能直接与底层的数据源(如文件、网络等)进行通信的,需要使用转换流进行转换。
InputStreamReader:将字节输入流转换为字符输入流
OutputStreamWriter:将字节输出流转换为字符输出流
使用转换流的最常见场景为需要对读取到的字节数据进行指定编码方式的转换,或对读取到的字符数据进行指定编码方式的转换。例如,下面的代码片段实现了将一个GBK编码的文件读取为UTF-8编码的字符串:
try (InputStream in = new FileInputStream("gbk.txt");
InputStreamReader isr = new InputStreamReader(in, "GBK");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(baos, "UTF-8")) {
char[] buffer = new char[1024];
int len;
while ((len = isr.read(buffer)) != -1) {
osw.write(buffer, 0, len);
}
osw.flush();
String result = baos.toString("UTF-8");
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
序列化流
序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以是字节或者XML等格式。序列化可以将一个对象压缩成字节流,方便在网络上传输和存储到磁盘上,同时也能够保持对象的持久状态。
反序列化(Deserialization)则是将序列化后的数据转换回原始的数据结构或对象的过程。反序列化将存储或传输中的数据重新解析为程序中的内存对象,使得程序可以对这些数据进行操作和处理。
在 Java 中,序列化操作主要由 ObjectOutputStream 类负责实现,反序列化操作主要由 ObjectInputStream 类来实现。一个 Java 对象要想被序列化,必须先实现 Serializable 接口。该接口是一个标记接口,没有任何需要实现的方法。只要实现了 Serializable 接口的对象,就可以被 ObjectOutputStream 序列化。
下面是一个示例代码,演示了如何将一个Java对象进行序列化和反序列化:
// 定义一个可序列化的 Student 类
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
// 序列化和反序列化示例代码
public class SerializationDemo {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.ser"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.ser"))) {
// 将学生对象(Student)序列化
Student student = new Student("张三", 18);
oos.writeObject(student);
// 反序列化学生对象(Student)
Student result = (Student) ois.readObject();
System.out.println("name: " + result.getName() + ", age: " + result.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在上述示例中,我们定义了一个学生类(Student),并实现了 Serializable 接口。然后我们使用对象输出流(ObjectOutputStream)将学生对象(Student)序列化,并将其保存为文件(student.ser)。接着,我们使用对象输入流(ObjectInputStream)从文件中读取序列化的数据,并将其反序列化为学生对象。最后我们输出了反序列化后的学生对象的姓名和年龄。
需要注意的是,在进行对象序列化和反序列化时,要求被序列化的对象所属的类必须实现 Serializable 接口,否则会抛出 NotSerializableException 异常。此外,在网络传输过程中,需要注意序列化和反序列化时使用相同的协议版本,否则可能会导致反序列化失败。
对于不想序列化的变量,使用 transient 关键字修饰
- transient 只能修饰变量,不能修饰类和方法
- transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。如果是修饰 int 类型,那么反序列后结果就是 0。static 变量不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化
多线程
基本概念
- 并行:同一时刻,多个CPU同时执行多个任务,多个人同时做不同的事
- 并发:在同一时刻,一个CPU交替执行多个任务,秒杀、多个人做同一件事
- 进程:程序的基本执行实体,每一个正在运行的软件就是一个进程
- 线程:操作系统中能够运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位
使用场景:文件下载;后台任务;异步处理
实现方式
方式一:继承Thread类
该方式是最常见的实现Java多线程的方式。具体来说,就是创建一个继承自Thread类的子类,在子类中重写run()方法,该方法就是子线程执行的逻辑代码。通过调用start()方法启动线程。
class MyThread extends Thread {
public void run() {
// 子线程执行任务的代码
}
}
MyThread myThread = new MyThread();
myThread.start();
一 个线程对象只能调用一次 start() 方法启动
方式二:实现Runnable接口
该方式比较灵活,因为一个类可以继承其他类,但只能实现一个接口。具体来说,就是创建一个实现了Runnable接口的类,在类中实现run()方法,该方法就是子线程执行的逻辑代码。通过创建Thread实例并将实现了Runnable接口的对象传入,调用start()方法启动线程。
class MyRunnable implements Runnable {
public void run() {
// 子线程执行任务的代码
}
}
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start();
VS 方式一:没类的单继承性的局限性;多个线程可以共享同一个接口实现类的对象,更适合来处理多个线程共享数据的情况
方式三:实现Callable接口
相对于Runnable接口,Callable接口可以定义有返回值的子线程任务。call() 方法代替了 run() 方法。调用FutureTask.get() 方法可以获取线程执行结束后的返回值。
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 子线程执行任务的代码
return 123;
}
}
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread myThread = new Thread(futureTask);
myThread.start();
Integer result = futureTask.get();
成员方法
-
抢占式调度:随机
-
线程优先级,默认5(最小1,最大10)
线程分类
-
守护线程:当其他的非守护线程执行完毕之后,守护线程会陆续结束
- 应用场景:QQ聊天并且发文件,关闭聊天窗口发送文件就取消
-
出让/礼让线程:出让CPU的执行权
-
插入/插队线程:插入到当前线程之前执行
生命周期
线程同步
方式一:同步代码块
该类继承thread类会创建多个对象,如需共享资源,成员变量需要加static静态关键字修饰
synchronized(要同步的对象){ 要同步的操作 }
锁对象一定要是唯一的,一般把当前类的字节码文件对象作为锁对象:类.class
方式二:同步方法
private synchronized void method(){ 要同步的操作 }
锁对象不能自己指定
- 非静态:this
- 静态:当前类的字节码文件对象
方式三:Lock锁 (更灵活的代码控制)
实现类对象:ReentrantLock
- lock.lock(); //锁
- lock.unlock(); //释放锁
加锁释放锁当中如果有循环语句,需要加上try catch finally在最后释放锁,有可能锁资源得不到释放,程序无法停止
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决死锁:
- 剥夺某些进程所占有的资源
- 撤消某些进程
线程通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- 令当前线程挂起并放弃 CPU 、 同步资源并等待, 使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用 notify() 或 notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
注意:
sleep() 和 wait()
相同:一旦执行方法,都可以使当前的线程进入阻塞状态
不同:
- 两个方法声明的位置不同:Thread类中声明sleep(), Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景先调用。wait()必须在同步代码块或同步方法中调用
- 是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
线程池
Java线程池是一种可以管理和复用线程的机制。在多线程应用中,不断地创建和销毁线程会带来较大的开销和时间消耗,在某些情况下,还可能会导致系统崩溃。因此,使用线程池可以避免频繁地创建和销毁线程。
组成部分
- 任务队列:存放等待执行的任务
- 线程池管理器:用于创建线程池并监控线程池中线程的状态
- 工作线程:处理任务的线程
- 任务接口:需要执行的任务都实现该接口
实现流程
- 创建线程池对象,设置相关参数
- 创建任务对象实例
- 将任务对象提交到线程池中执行
- 关闭线程池 (一般不会关闭)
通过 ThreadPoolExecutor 创建
推荐通过 new ThreadPoolExecutor() 的写法创建线程池,这样写线程数量更灵活,开发中多数用这个类创建线程
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize :线程池中核心线程数的最大值
maximumPoolSize :线程池中能拥有最多线程数
workQueue:用于缓存任务的阻塞队列
当调用线程池execute() 方法添加一个任务时,线程池会做如下判断:
1、如果有空闲线程,则直接执行该任务;
2、如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;
3、如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;
4、如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;
5、如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。
核心线程数 (不能小于0)
最大线程数 (最大数量>=核心线程数)
空闲线程存活时间 (不能为0)
空闲线程存活时间的单位 (用TimeUnit指定)
任务队列 (不能为null)
线程工厂 (不能为null)
拒绝策略 (不能为null)
阻塞/任务队列
ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无界,最大为int的最大值
拒绝策略
ThreadPoolExecutor.AbortPolicy:默认策略,丢弃任务并抛出异常
ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛出异常,不推荐
ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,把当前任务加入队列
ThreadPoolExecutor.CallerRunPolicy:调用任务的run()方法绕过线程池直接执行
成员方法
submit:可以执行有返回值的任务或者是无返回值的任务
execute:只能执行不带返回值的任务
通过 Executors 创建
- Executors.newFixedThreadPool:创建有上限的线程池,创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待
- Executors.newCachedThreadPool:创建一个没有上限的线程池,创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序
- Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池
- Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池
- Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
线程池多大合适
CPU 密集型运算:项目中计算比较多,读取文件或数据库的操作比较少
- 最大并行数+1
I/O 密集型运算:读取文件或数据库的操作比较多,大部分是这种
网络编程
软件架构
C/S 客户端/服务器
1. 画面可以做的非常精美,用户体验好
2. 需要开发客户端,也需要开发服务端
3. 用户需要下载和更新的时候太麻烦
B/S 浏览器/服务器
1. 不需要开发客户端,只需要页面+服务端
2. 用户不需要下载,打开浏览器就能使用
3. 如果应用过大,用户体验收到影响
三要素
IP地址
设备在网络中的地址,是唯一的标识
IP分类
IPv4:互联网通信协议第四版,32位地址长度,分成四组
IPv6:互联网通信协议第六版,128位地址长度,分成8组
IP形式
- 公网地址、私有地址 (局域网使用)
- 192.168. 开头的就是常见的局域网地址
- 范围为192.168.0.0–192.168.255.255,专门为组织机构内部使用
常用命令
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
- 本机IP:127.0.0.1或者localhost,称为回送地址或本地回环地址
IP操作类
端口
应用程序在设备中唯一的标识
协议
数据在网络中传输的规则,常见协议有UDP、TCP、http、https、ftp
TCP
三次握手建立连接
四次挥手断开连接
UDP
单播:一对一的发送数据
组播:一对多,给一组电脑发送数据
- 组播地址:224.0.0.0~239.255.255.255
- 预留的组播地址:224.0.0.0~224.0.0.255
广播:一对多,可以给局域网中所有电脑发送数据
- 广播地址:255.255.255.255
Java其他内容
泛型
Java泛型是在JDK 5中引入的一种新特性,它提供了参数化类型的概念,可以让我们编写更加安全、清晰、易读并且重用性更强的代码。通过使用泛型可以在编译时对类型进行检查,避免了运行时的类型转换错误,提高了代码的可读性和灵活性。
Java泛型的基本语法格式如下:
class ClassName<T, U> {
// 声明泛型变量
T variable1;
U variable2;
void method(T arg1, U arg2) {
// 方法体
}
}
在上面的示例中,ClassName
类中使用了两个泛型变量 T
和 U
,它们可以代表任何Java数据类型。在类中可以使用这些泛型变量来声明成员变量、方法参数和返回类型等,从而达到可以适用于不同数据类型的目的。
几个常见的泛型通配符:
?
:代表任意类型E
:代表元素类型,常用于集合框架中K
:代表键的类型V
:代表值的类型T
:代表任意类型,常用于泛型方法中
Java泛型还有一个重要的概念是通配符和边界,在定义泛型类型时指定泛型的边界,以限制泛型类型的取值范围。
<T extends Number>
:T 可以是 Number 或其子类,不能是其他类<T super String>
:T 可以是 String 或其父类,不能是其他类
泛型擦除:编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作
Java泛型的优点:
- 提高类型安全性,减少类型转换错误
- 提高代码可读性和重用性
- 减少代码量,消除重复代码
枚举
Java枚举是一种特殊的数据类型,它可以定义一组预定义的常量。Java枚举通常用于表示一组相关的常量,例如星期、月份、颜色等。Java中使用enum
关键字来定义枚举类型,其基本语法如下:
enum EnumName {
ENUM_CONSTANT1,
ENUM_CONSTANT2,
ENUM_CONSTANT3,
//...
}
其中,EnumName
是枚举的名称,ENUM_CONSTANT1
、ENUM_CONSTANT2
等均为枚举常量,可以定义多个常量,常量之间使用逗号分隔,最后一个常量后面不需要加逗号。
Java枚举的主要优点有:
- 安全性高:Java枚举类型只能包含枚举值,不能包含其他类型的值,可以避免无意义的赋值
- 可读性好:Java枚举可以让代码更易于理解和维护,使得程序员能够用类似于自然语言的方式描述代码的含义
- 扩展性强:Java枚举可以非常容易地添加新的值,而不需要修改已有的代码,这使得代码更加灵活和可扩展
Java枚举类型也可以实现接口、扩展其他类,并且可以具有自己的属性和方法。Java枚举类型还可以使用values()
方法获取枚举常量数组、valueOf()
方法将字符串转换为枚举常量。在Java 8之后,还可以使用forEach()
和stream()
等方法来遍历枚举。
Java枚举的典型使用场景包括:
- 代替常量:使用枚举可以代替一些用于存储常量的类或者接口,使代码更具可读性
- 枚举器实现:向前兼容,允许新的枚举值添加到现有的代码中,而且不会损坏旧代码
- 状态机模式:枚举可以用于定义状态机模式中的状态
- 工厂模式:Java枚举可以方便地作为工厂模式的一种实现方式
注解
Java注解(Annotation)是一种元数据信息,它被用于声明在类、方法、变量、参数等代码元素上,以提供给编译器、IDE或者其他工具使用的额外信息。Java注解是从JDK 5开始引入的一种注释机制,它不同于传统的Java注释,注解可以通过反射获取信息并嵌入到字节码中,在运行时也可以获取到注解内容。
Java注解以@
符号开头,后面紧跟着注解名称和一组圆括号,圆括号内可以包含一些可选的参数值。注解名称通常以大写字母开头,Java中内置了多个标准注解,例如@Override
、@Deprecated
和@SuppressWarnings
等,还可以自定义注解类型来满足特定的需求。
Java注解有三种基本类型:
@Retention
:用来指定注解的保留策略,决定了注解的生命周期,可以取值为SOURCE
、CLASS
或RUNTIME
@Target
:用来指定注解可以被应用于哪些程序元素上,例如TYPE
、METHOD
、FIELD
、PARAMETER
等@Documented
:用来指定注解是否能够被文档化,如果指定了该注解,那么在使用javadoc生成API文档时,该注解信息将包含在文档中
Java注解的其他用途包括:
- 提供编译时检查:可以利用注解进行编译时的类型检查和限制,例如使用
@SuppressWarnings
注解来抑制编译器的警告信息。 - 代码分析和生成工具:可以使用注解与代码生成和分析工具集成,例如使用Lombok库中的注解来简化Java代码、自动生成构造函数和访问器等。
- 给框架提供信息:通过注解,框架可以自动地完成诸如依赖注入、AOP、事务管理等任务。
反射
Java反射是在运行时动态地获取类的信息,并可以在运行时通过该类的接口调用对象方法、访问或者修改对象属性,最终实现动态创建对象、调用方法和处理异常等功能。Java反射机制是Java语言的一个特性,它允许在运行时检查类、接口、方法和变量等信息,并且可以在运行时修改这些信息。
Java反射主要通过三个关键类来实现:
Class
类:用于描述一个类的基本信息,包括类名、父类、实现的接口、类中的字段和方法Constructor
类:用于描述一个类的构造方法,在Java反射中可以使用该类来创建新的对象Method
类:用于描述类中的方法,在Java反射中可以使用该类来调用对象方法
Java反射可以带来以下一些好处:
- 动态创建对象:可以通过 Class 类的 newInstance() 方法动态创建对象,而不需要使用 new 关键字
- 动态获取类信息:可以在运行时动态获取类的名称、父类、实现的接口、包名等信息
- 动态调用方法:可以在运行时动态地调用类的某个方法
- 动态获取字段:可以在运行时动态地获取类的某个字段
Java反射也有一些缺点,主要包括以下几点:
- 反射的性能较低:反射机制需要进行额外的类型检查,而这些操作会对性能产生一定的影响
- 反射的安全性问题:反射机制可以破坏程序的封装性和安全性,容易受到恶意攻击
- 反射使用较为复杂:反射代码通常比普通代码更难以阅读和维护,使用不当容易导致错误
获取Class对象
- 类名.class
- 对象名.getClass()
- Class.forName(全类名)
获取对象
创建对象的方法
- T newInstance(Object… initargs):根据指定的构造器创建对象
- public void setAccessible(boolean flag):设置为true,表示取消访问检查,进行暴力反射
调用成员方法的方法
给成员变量赋值的方法
- void set(Object obj, Object value):赋值
- Object get(Object obj):获取值
反射案例,展示了如何动态地创建对象、获取字段信息和调用对象方法:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 动态创建对象
Class<?> cls = Class.forName("Person");
Object person = cls.newInstance();
// 获取并设置字段值
Field nameField = cls.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(person, "张三");
Field ageField = cls.getDeclaredField("age");
ageField.setAccessible(true);
ageField.setInt(person, 20);
// 调用对象方法
Method sayHelloMethod = cls.getDeclaredMethod("sayHello");
sayHelloMethod.setAccessible(true);
sayHelloMethod.invoke(person);
Method sayNameMethod = cls.getDeclaredMethod("sayName");
sayNameMethod.setAccessible(true);
sayNameMethod.invoke(person);
}
}
class Person {
private String name;
private int age;
public void sayHello() {
System.out.println("Hello World!");
}
private void sayName() {
System.out.println("My name is " + name + ", I'm " + age + " years old.");
}
}
在上述示例中,首先使用Class.forName()
方法动态地获取了Person
类的信息,并调用newInstance()
方法创建了一个新的Person
对象。然后,使用getDeclaredField()
方法获取了name
和age
两个字段,并通过setAccessible(true)
方法将它们设为可访问的。接着,使用set()
方法分别设置了name
和age
字段的值。
接下来,使用getDeclaredMethod()
方法获取了sayHello()
和sayName()
两个方法,并同样使用setAccessible(true)
方法将它们设为可访问的。最后,使用invoke()
方法分别调用了这两个方法,并打印出了相应的输出结果。
lambda表达式
Lambda表达式是Java SE 8的一个新特性,它是一个匿名函数,可以像其他任何对象一样传递和使用。Lambda表达式允许我们把函数作为方法参数或者将代码作为数据对待。
在Java中,Lambda表达式的语法形式如下:
(parameter) -> expression
或者
(parameter) -> {
statements; }
其中,parameter
是参数列表,expression
是单个语句的表达式,statements
是多个语句的语句块。Lambda表达式可以使用任意数量的参数,以及通过逗号分隔的多个代码块。
Lambda表达式可以再任何需要接口类型的地方使用,也被称为函数式接口,即只有一个抽象方法的接口。Lambda表达式可以简化匿名内部类的写法,使代码更加简洁易懂。例如,使用Lambda表达式实现Runnable接口可以写成:
Runnable r = () -> System.out.println("Hello World!");
在Lambda表达式中,箭头左边的部分表示方法的参数,右边的部分表示方法体,这里使用了单行语句的表达式。也可以使用语句块的形式,例如:
Runnable r = () -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
};
方法引用
Java方法引用是一种非常强大的函数式编程特性,它可以简化代码并提高代码可读性。在Lambda表达式中,方法引用可以被认为是Lambda表达式的简写形式,可以将一个方法作为值传递,而无需显式地编写Lambda表达式。
Java方法引用有四种类型:
- 静态方法引用:引用静态方法
- 实例方法引用:引用某个实例对象的方法
- 构造函数引用:引用构造函数,可以创建新的对象
- 数组构造器引用:引用数组构造器
语法形式如下:
- 静态方法引用:
ClassName::staticMethodName
- 实例方法引用:
instance::methodName
或者ClassName::methodName
- 构造函数引用:
ClassName::new
- 数组构造器引用:
TypeName[]::new
下面通过示例来说明这四种方法引用的使用。
- 静态方法引用:
public class MethodReferenceExample {
public static void sayHello(String name) {
System.out.println("Hello " + name);
}
public static void main(String[] args) {
Consumer<String> consumer = MethodReferenceExample::sayHello;
consumer.accept("John");
}
}
在上述示例中,我们定义了一个静态方法sayHello()
,然后使用方法引用将其传递给了一个Consumer实例。Consumer实例的accept方法和sayHello()
方法参数列表一致。当我们调用Consumer的accept方法时,实际上会调用sayHello()
方法。
- 实例方法引用:
public class MethodReferenceExample {
public void sayHello(String name) {
System.out.println("Hello " + name);
}
public static void main(String[] args) {
MethodReferenceExample instance = new MethodReferenceExample();
Consumer<String> consumer = instance::sayHello;
consumer.accept("John");
}
}
在上述示例中,我们定义了一个实例方法sayHello()
,然后使用方法引用将其传递给了一个Consumer实例。与静态方法引用不同,此处需要先创建一个类的实例,然后使用实例方法引用。当我们调用Consumer的accept方法时,实际上会调用sayHello()
方法。
- 构造函数引用:
public class MethodReferenceExample {
public static void main(String[] args) {
Supplier<StringBuilder> supplier = StringBuilder::new;
StringBuilder sb = supplier.get();
sb.append("Hello World!");
System.out.println(sb.toString());
}
}
在上述示例中,我们使用构造函数引用创建了一个StringBuilder对象,相当于执行了new StringBuilder()
语句。然后我们向StringBuilder对象中添加了一些文本,并打印出最终结果。
- 数组构造器引用:
public class MethodReferenceExample {
public static void main(String[] args) {
Function<Integer, String[]> function = String[]::new;
String[] arr = function.apply(10);
System.out.println(arr.length);
}
}
在上述示例中,我们使用数组构造器引用创建了一个长度为10的String数组,然后打印出了数组的长度。
Stream流
Java Stream是Java8中引入的一种新的API,它可以让开发者以一种更加直观、高效、声明式的方式处理集合数据。Stream提供了一系列的操作(包括中间操作和终止操作),可以实现数据的过滤、转换、分组、聚合等操作。与传统的集合操作相比,Stream具有更高的抽象层次,可以更加方便地对集合进行并行处理。
Java Stream本质上是一个函数式接口,它提供了很多方法来创建、操作和处理流。创建流的方式可以通过集合、数组等数据源,也可以通过Stream API提供的静态工厂方法和生成器方法来创建。一旦创建了流,就可以对其进行一系列的操作(如过滤、转换、排序、聚合等操作)。然后通过终止操作,可以将结果保存到集合或其他数据结构中,也可以直接打印出来等。
Stream具有以下特点:
- Stream API支持函数式编程,可以通过Lambda表达式直接传递函数
- Stream API支持链式操作,可以使代码更加简洁易读
- Stream API支持并行处理,可以提高运算速度
- Stream API支持延迟加载,可以尽可能地推迟计算,从而提高效率
步骤
- 创建 Stream (一个数据源(如:集合、数组),获取一个流)
- 中间操作 (一个中间操作链,对数据源的数据进行处理)
- 终止操作 ( 一旦执行终止操作,就终止中间操作链,并产生结果)
注意
- Stream 自己不会存储元素
- Stream 不会改变源对象
- Stream 操作是延迟执行的
中间操作
filter 筛选:Stream<Integer> stream = integerList.stream().filter(i -> i > 3);
distinct 去重:Stream<Integer> stream = integerList.stream().distinct();
limit 返回指定流个数:Stream<Integer> stream = integerList.stream().limit(3);
skip 跳过流中的元素:Stream<Integer> stream = integerList.stream().skip(2);
concat 合并流:Stream.concat(Stream a, Stream b)
map 流映射:
List<Integer> collect = stringList.stream()
.map(String::length)
.collect(Collectors.toList());
flatMap 流转换:
List<String> strList = wordList.stream()
.map(w -> w.split(" "))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
allMatch 匹配所有元素:integerList.stream().allMatch(i -> i > 3)
anyMatch匹配其中一个:integerList.stream().anyMatch(i -> i > 3)
noneMatch全部不匹配:integerList.stream().noneMatch(i -> i > 3)
终端操作
foreach 遍历:stringList.stream().forEach(System.out::println);
count 统计元素个数:Long result = integerList.stream().count();
findFirst 查找第一个
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
findAny 随机查找一个
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
reduce 将流中的元素组合:int sum = integerList.stream().reduce(0, Integer::sum);
toArray() 返回数组:Object[] arr = list.stream().toArray();
collect 返回集合:
List<Integer> intList = stringList.stream()
.map(String::length)
.collect(Collectors.toList());
.map(String::length)
.collect(Collectors.toList());
flatMap 流转换:
List<String> strList = wordList.stream()
.map(w -> w.split(" "))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
allMatch 匹配所有元素:integerList.stream().allMatch(i -> i > 3)
anyMatch匹配其中一个:integerList.stream().anyMatch(i -> i > 3)
noneMatch全部不匹配:integerList.stream().noneMatch(i -> i > 3)
终端操作
foreach 遍历:stringList.stream().forEach(System.out::println);
count 统计元素个数:Long result = integerList.stream().count();
findFirst 查找第一个
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();
findAny 随机查找一个
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();
reduce 将流中的元素组合:int sum = integerList.stream().reduce(0, Integer::sum);
toArray() 返回数组:Object[] arr = list.stream().toArray();
collect 返回集合:
List<Integer> intList = stringList.stream()
.map(String::length)
.collect(Collectors.toList());
Java 工具类
java.util.Arrays
sort(T[] a)
:对数组 a 中的元素进行排序binarySearch(T[] a, T key)
:在已排序的数组 a 中查找元素 key 的位置copyOf(T[] original, int newLength)
:将原数组 original 复制到新数组中,并指定新数组的长度为 newLength
java.util.Collections
sort(List<T> list)
:对列表 list 中的元素进行排序binarySearch(List<? extends Comparable<? super T>> list, T key)
:在已排序的列表 list 中查找元素 key 的位置reverse(List<?> list)
:将列表 list 中元素顺序反转shuffle(List<?> list)
:随机打乱列表 list 中元素的顺序
java.text.SimpleDateFormat
format(Date date)
:将日期对象 date 格式化为字符串,可以指定格式parse(String source)
:将字符串 source 解析为日期对象
java.util.Calendar
get(int field)
:获取日历字段的值,如年、月、日、时、分、秒等set(int field, int value)
:设置日历字段的值add(int field, int amount)
:对日历字段进行加减操作
java.net.URL
openConnection()
:打开与此 URL 的连接getContent()
:获取 URL 的内容toString()
:返回此 URL 的字符串表示形式
java.io.File
exists()
:判断文件是否存在createNewFile()
:创建新文件delete()
:删除文件listFiles()
:列出目录下的所有文件
java.util.StringTokenizer
hasMoreTokens()
:判断是否还有分隔符后面的字符串nextToken()
:获取分隔符后面的字符串
java.util.Random
nextInt()
:返回一个随机整数nextDouble()
:返回一个随机双精度浮点数setSeed(long seed)
:设置随机数生成种子
org.apache.commons.lang3.StringUtils
isBlank(CharSequence cs)
:判断字符串是否为空或全为空格trim(CharSequence cs)
:去除字符串首尾的空格join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
:使用指定的分隔符将元素拼接成字符串
org.apache.commons.codec.binary.Base64
encodeBase64String(byte[] binaryData)
:将二进制数据编码为Base64格式的字符串decodeBase64(String base64String)
:将Base64格式的字符串解码为原始的二进制数据
文章评论