Java入门篇
Java 是一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承、指针等概念,因此,Java 语言具有功能强大和简单易用两个特征。Java 语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java 具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。
Java 可以编写桌面应用程序、Web 应用程序、分布式系统和嵌入式系统应用程序等。
编程环境配置
正所谓,工欲善其事必先利其器,在开发 Java 语言过程中同样需要一款不错的开发工具。目前市场上的 IDE 很多,常用的有:
- IntelliJ IDEA: 功能很强大。下载地址:https://www.jetbrains.com/idea/download/。
- VSCode: 一款由微软开发且跨平台的免费源代码编辑器。下载地址:https://code.visualstudio.com/。
- Eclipse: 下载地址: http://www.eclipse.org/downloads/packages/。
- Netbeans: 下载地址: http://www.netbeans.org/index.html。
推荐使用 IntelliJ IDEA。
第一个 Java 程序
新建 Java 项目
IntelliJ IDEA -> 文件-> 新建 -> 项目,输入项目名称、路径、语言等信息,结构如下:
-src
-Main.java
-.gitignore
-java.iml
public class Main {
public static void main(String[] args) {
System.out.println(args[0] + " " + args[1]);
System.out.println(System.getenv("DB_URL"));
System.out.println(System.getenv("DB_PASSWORD"));
}
}
一个 .java 文件中:
- 可以有多个类
- 但最多只能有一个
public class - 如果有
public class,类名必须与文件名完全相同
Debug
IntelliJ IDEA -> 运行 -> 调试:
- 名称: 为运行配置指定一个名称,以便在编辑或运行时在其他配置中快速识别它。
- 运行于: 选择要在其中运行配置的目标环境。如:本地运行、SSH(远程)、Docker 容器等。
- 存储为项目文件: 默认位置是:
.idea/runConfigurations,也可将保存到项目中的任何其他目录。默认是禁用的,运行配置设置存储在.IDEA /workspace.xml中。 - Java 版本:指定运行的 Java 版本。
- 主类:当前运行的入口类,这里只显示类名。完整类名可能如
Main或com.example.Main。 - 程序实参:启动程序时,跟在类名后面直接输入的那些字符串。
- 工作目录: 即所有相对输入和输出路径的起点。默认情况下,为项目根目录。
- 环境变量: 操作系统层面的全局键值对,属于进程运行环境的一部分。所有由当前进程启动的子进程都会继承这些变量。

注意: 必须先将 src 标识为源代码。 IntelliJ IDEA -> 文件 -> 项目结构:

生成 Jar 包
IntelliJ IDEA -> 文件 -> 项目结构 -> 工件 -> "+" -> Jar -> 从具有依赖项的模块:

IntelliJ IDEA -> 运行 -> 构建 -> 编译工件,选择配置再点 ”构建“,生成 .jar 文件,默认在根目录的 out 文件夹下。
运行 Jar 包
java -jar .\out\artifacts\main_jar\java-demo.jar
修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。
Java 修饰符主要分:访问修饰符、非访问修饰符。
访问控制修饰符(4个)
访问控制符用来保护对类、变量、方法和构造方法的访问。
private、default、protected、public 修饰符:
| 修饰符 | 同类 | 同包 | 子类(不同包) | 任何类 | 说明 |
|---|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ | 仅本类可见 |
default(无修饰符) |
✅ | ✅ | ❌ | ❌ | 包级私有 |
protected |
✅ | ✅ | ✅ | ❌ | 包+子类 |
public |
✅ | ✅ | ✅ | ✅ | public完全公开 |
private
最严格的访问控制,被修饰的成员只能在同一个类内部被访问,对于任何外部类(包括子类和其他包中的类)都不可见。常用于封装内部实现细节,避免外部直接修改。
default
也称包级私有,即不写任何修饰符。被修饰的成员可以在同一个包内的任何类中被访问,但包外部的类(即使是子类)无法访问。当没有明确指定访问修饰符时,即为此级别。
protected
介于 default 和 public 之间,被修饰的成员可以被同一个包内的任何类访问,并且可以被不同包中的子类(通过继承关系)访问。常用于允许子类重写或使用父类的核心方法。
public
最宽松的访问控制,被修饰的成员对所有类完全可见,无论是否在同一个包中,或是否存在继承关系。
package com.example;
public class ModifierDemo {
private int a = 1; // 仅本类
int b = 2; // default:同包可见
protected int c = 3; // 同包+子类可见
public int d = 4; // 完全公开
private void method1() { } // 私有方法
void method2() { } // default方法
protected void method3() { } // protected方法
public void method4() { } // 公开方法
}
// 同包下的另一个类
class SamePackage {
void test() {
ModifierDemo md = new ModifierDemo();
// md.a = 1; ❌ 错误,private不可见
md.b = 2; // ✅ default可见
md.c = 3; // ✅ protected可见
md.d = 4; // ✅ public可见
}
}
访问控制方法继承的规则:
- 父类中声明为 public 的方法在子类中也必须为 public。
- 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public。不能声明为 private。
- 父类中声明为 private 的方法,不能够被继承。
非访问修饰符(6个)
static
静态。属于类本身,而不是属于某个对象,也就是说,所有对象共享同一个静态成员。
- static 变量:静态变量。所有对象共享。
- static 方法:静态方法。可以访问静态变量,不能直接访问实例变量。
- static 类:静态类。不依赖外部类实例就能使用的内部类。注意:不能修饰顶层类,只能修饰内部类。
final
不可变。即一旦赋值就不能改变。
- final 类:不能被继承。
- final 方法:不能被重写。
- final 变量:只能赋值一次(常量)。
abstract
抽象。只有声明,没有实现,必须被继承/实现。
- 抽象类:声明抽象类的唯一目的是为了将来对该类进行扩充。
- 抽象方法:一种没有任何实现的方法,该方法的的具体实现由子类提供。
abstract 规则:
- 有抽象方法的类必须是抽象类。
- 抽象类可以没有抽象方法。
- 抽象类不能实例化。
- 子类必须实现所有抽象方法,除非子类也是抽象的。
synchronized
同步。多线程环境下,保证同一时刻只有一个线程执行该方法/代码块。
volatile
易变,表示变量可能被多个线程同时修改,不要缓存,每次都从主内存读取。
transient
瞬时。被修饰的变量不会被序列化,用于标记不需要保存的字段。序列化,是指 Java 对象转换成字节流(可以保存到文件或通过网络传输)。
import java.io.*;
class User implements Serializable {
String name = "张三"; // 会被保存
transient String password = "123"; // 不会被保存
}
public class Test {
public static void main(String[] args) throws Exception {
User user = new User();
// 保存对象到文件
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("data.txt"));
out.writeObject(user);
out.close();
// 从文件恢复对象
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("data.txt"));
User user2 = (User) in.readObject();
in.close();
// 查看结果
System.out.println("name: " + user2.name); // 张三
System.out.println("password: " + user2.password); // null
}
}
使用场景:
- 密码、token 等敏感信息(不想持久化)。
- 缓存数据(重新计算即可)。
- 派生字段(可以根据其他字段计算)。
基本数据类型
Java 语言提供了八种基本类型:六种数字类型(四个整数型,两个浮点型)、一种字符类型、还有一种布尔型。
byte:1字节,范围 -128~127。short:2字节,范围 -32768~32767。int:4字节,范围 -2^31 ~ 2^31-1(最常用)。long:8字节,范围 -2^63 ~ 2^63-1(使用时需加 L 后缀)。float:4字节,单精度,范围约 ±3.4E-45 ~ ±3.4E+38(需加 F/f 后缀)。double:8字节,双精度,范围约 ±4.9E-324 ~ ±1.8E+308(默认浮点类型,D/d 后缀可选)。char:2字节,使用单引号表示,存储 Unicode 字符(如'A'、'中'、'\u0000')。boolean:大小未精确定义(通常1字节或1位),只有两个值:true和false。
八种基本类型
字节型(byte)
字节型是 8 位、有符号的,以二进制补码表示的整数。其范围是 -128(-2^7)~127(2^7-1),默认值是 0。字节型变量是整型变量所占空间的四分之一。
public static void printByte() {
byte a = 100;
byte b = -50;
System.out.println(a);
System.out.println(b);
}
数值超过范围,报错:java: 不兼容的类型: 从int转换到byte可能会有损失;
短整型(short)
短整型是 16位、有符号的以二进制补码表示的整数。其范围是 -32768(-2^15)~ 32767(2^15 - 1),默认值是0。短整型变量是整型变量所占空间的二分之一。
public static void printShort() {
short a = 100;
short b = 0100;
short c = 0x100;
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
整型(int)
整型是32位、有符号的以二进制补码表示的整数。其范围是 -2,147,483,648(-2^31)~2,147,483,647(2^31 - 1),默认值是 0。
public static void printInt() {
int a = 1;
System.out.println(a);
}
长整型(long)
长整型是 64 位、有符号的以二进制补码表示的整数。其范围是 -9,223,372,036,854,775,808(-2^63)~9,223,372,036,854,775,807(2^63 -1),默认值是 0L。
这种类型主要使用在需要比较大整数的系统上。
public static void printLong() {
long a = 100000L;
System.out.println(a);
}
注意: 长整型带有标志性符号 “L” 或 "l"。如果不带,默认为整型,当赋值一个超过整型范围的值,会出错。
注意: “L" 理论上不分大小写,但是若写成 "l" 容易与数字 "1" 混淆,不容易分辩。所以最好大写。
单精度浮点型(float)
单精度浮点型是单精度、32位、符合 IEEE 754 标准的浮点数,默认值是 0.0f。
单精度浮点型默认值是 0.0f。
public static void printFloat() {
float a = 234.5f;
System.out.println(a);
}
注意: 单精度浮点型带有标志性符号 "F" 或 "f",否则系统会认为是双精度浮点型。
注意: 单精度浮点型不能用来表示精确的值,如货币。
双精度浮点型(double)
双精度浮点型是双精度、64位、符合 IEEE 754 标准的浮点数,默认值是 0.0d。
public static void printDouble() {
double a = 234.51111d;
System.out.println(a);
}
注意:双精度浮点型带有标志性符号 "D" 或 "d"。系统默认不带标志性符号的浮点型是双精度浮点型。
注意: 双精度浮点型也不能用来表示精确的值,如货币。
字符型(char)
字符型是一个单一的 16 位 Unicode 字符,只能表示单个字符。其范围 ’\u0000’(0)~ ’\uffff’(65,535)。
public static void printChar() {
char a = '兆';
System.out.println(a);
}
注意: 字符型必须以单引号表示,如:'兆' 表示一个字符,而 ”兆“ 表示一个字符串(虽然只有一个字 符)。
布尔型(boolean)
布尔型表示 1 位的信息。其只有两个取值:true 和 false,默认值是 false。
public static void printBoolean() {
boolean a = false;
System.out.println(a);
}
基本类型的转换
Java 的基本类型转换分为两大类:自动类型转换(隐式转换)和强制类型转换(显式转换)。
自动类型转换(隐式转换)
小范围类型自动转换为大范围类型(无精度损失),转换方向:byte → short → int → long → float → double。
注意:char 可以转换为 int 及以上类型。
byte b = 100;
short s = b; // byte 自动转 short
int i = s; // short 自动转 int
long l = i; // int 自动转 long
System.out.println(s);
System.out.println(i);
System.out.println(l);
int num = 100;
float f = num; // int 自动转 float
double d = f; // float 自动转 double
System.out.println(f);
System.out.println(d);
char c = 'A';
int code = c; // char 自动转 int
System.out.println(code); // 65
byte b2 = 127; // 常量在byte范围内,自动转换
short s2 = 32767; // 常量在short范围内,自动转换
byte b3 = 10;
int i3 = 20;
int result = b3 + i3; // byte自动提升为int
System.out.println(result);
强制类型转换(显式转换)
大范围类型强制转为小范围类型(可能丢失精度或溢出)。
double d = 123.456;
float f = (float) d; // 强制转float,可能丢失精度
long l = (long) d; // 舍弃小数部分
int i = (int) d;
short s = (short) d;
byte b = (byte) d;
System.out.println(f);
System.out.println(l);
System.out.println(i);
System.out.println(s);
System.out.println(b);
int i2 = 300;
byte b2 = (byte) i2; // 300超出byte范围,发生溢出
System.out.println(b2); // 44(300 - 256 = 44)
double d2 = 3.999;
int i3 = (int) d2; // 直接截断小数
System.out.println(i3); // 3
int charCode = 65;
char c = (char) charCode; // int 转 char
System.out.println(c); // 'A'
int a = 10, b3 = 3;
double result = (double) a / b3; // 先转 double 再除
System.out.println(result); // 3.333...
运算符
运算符用于变量或常量运算。
常用的有10种:算术运算符、关系运算符、逻辑运算符、位运算符、移位运算符、赋值运算符、三元运算符、逗号运算符、字符运算符、转型运算符。
算术运算符(也称数学运算符)(8个)
算术运算符用在数学表达式中,它们的作用和在数学中的作用一样。其返回结果是数字。
+ 加法
- 减法
* 乘法
/ 除法
% 取模
++ 自增
-- 自减
- 反号
int a = 100;
double b = 200D;
System.out.println(a + b);
// 300.0
注意: 在不同类型的的数据之间进行运算时,为了使结果更加精确,系统会将结果自动转化为精度更高的数据类型。
关系运算符(6个)
关系运算符用于运算两个操作数据之间的关系。其返回结果是布尔型数据。
== 相等,则为真
!= 不相等,则为真
> 左操作数大于右操作数,则为真
< 左操作数小于右操作数,则为真
>= 左操作数大于或等于右操作数,则为真
<= 左操作数小于或等于右操作数,则为真
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a == b);
System.out.println(a != b);
System.out.println(a > b);
System.out.println(a < b);
System.out.println(a >= b);
System.out.println(a <= b);
}
}
注意:关系运算符也可以用于字符类型(char),因为字符在底层对应的是 Unicode 编码值。但不能用于字符串 (String),字符串要用 equals() 方法。
public class Test {
public static void main(String[] args) {
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 == c2);
System.out.println(c1 != c2);
System.out.println(c1 < c2);
System.out.println(c1 > c2);
// 查看字符的编码值
System.out.println((int)c1); // 65
System.out.println((int)c2); // 66
}
}
注意:字符可以和数字混合比较(自动类型转换),规则是:char 会先被转换成 int,然后再比较。
public class Test {
public static void main(String[] args) {
char c = 'A';
int num = 65;
System.out.println(c == num);
System.out.println("字符 'A' 的编码值: " + (int)c); // 65
}
}
逻辑运算符(3个)
逻辑运算符用于运算两个布尔型数据,一 般与关系运算符结合使用。
&& 与运算符。当且仅当两个操作数都为真,为真
|| 或操作符。当两个操作数任何一个为真,为真
! 非运算符。反转操作数的逻辑状态
public class Test {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
System.out.println(a && a); // true (真 && 真 = 真)
System.out.println(a && b); // false (真 && 假 = 假)
System.out.println(b && a); // false (假 && 真 = 假)
System.out.println(b && b); // false (假 && 假 = 假)
System.out.println(a || a); // true (真 || 真 = 真)
System.out.println(a || b); // true (真 || 假 = 真)
System.out.println(b || a); // true (假 || 真 = 真)
System.out.println(b || b); // false (假 || 假 = 假)
System.out.println(!a); // false (不是真)
System.out.println(!b); // true (不是假)
}
}
注意:逻辑运算符的两边必须是布尔值,否则编译报错。
位运算符
位运算符用于字节型、短整型、整数类型、长整型和字符型等类型,是针对两个操作数的二进制数的位进行运算。
& 按位与操作符,当且仅当两个操作数的某一位都非0时候结果的该位才为1
| 按位或操作符,只要两个操作数的某一位有一个非0时候结果的该位就为1
^ 按位异或操作符,两个操作数的某一位不相同时候结果的该位就为1
~ 按位非运算符,取相反的位值
<< 按位左移运算符,左操作数按位左移右操作数指定的位数,并在低位补0。相当于乘以2的右操作数次方
>> 按位右移运算符,左操作数按位右移右操作数指定的位数
>>> 按位右移补零操作符,左操作数的值按右操作数指定的位数右移,并在高位补0。相当于除以2的右操作数次方
注意: 后三个也称移位运算符。
int a = 60; // 0011 1100
int b = 25; // 0001 1001
System.out.println(a & b); // 0001 1000,即24
System.out.println(a | b); // 0011 1101,即61
System.out.println(a ^ b); // 0010 0101,即36
System.out.println(a << 2); // 1111 0000,即240
System.out.println(a >> 2); // 0000 1111,即15
System.out.println(a >>> 2); // 0000 1111,即15
注意: byte、short、char 类型的整数在移位操作前,会自动转为 int 类型再操作移位。
注意: int 类型占 32 位,long 类型占 64 位,当对这两个类型移位超出位数时,相当于对要移动的位数取余再移位。
a << 32; // 相当于a << 0
a << 34; // 相当于a << 2
赋值运算符
赋值运算符就是将值赋给变量。
= 赋值运算符,将右操作数的值赋给左侧操作数
+= 加和赋值操作符,将左操作数和右操作数相加,再赋值给左操作数。如:C += A 等价于 C = C + A
-= 减和赋值操作符,将左操作数和右操作数相减,再赋值给左操作数。如:C -= A 等价于 C = C - A
*= 乘和赋值操作符,将左操作数和右操作数相乘,再赋值给左操作数。如:C *= A 等价于 C = C * A
/= 除和赋值操作符,将左操作数和右操作数相乘,再赋值给左操作数。如:C /= A 等价于 C = C / A
%= 余和赋值操作符,将左操作数和右操作数相乘,再赋值给左操作数。如:C %= A 等价于 C = C % A
<<= 左移位赋值运算符。 如:C <<= 2 等价于 C = C << 2
>>= 右移位赋值运算符。 如:C >>= 2 等价于 C = C >> 2
&= 按位与赋值运算符。 如:C &= 2 等价于 C = C & 2
^= 按位异或赋值操作符。如:C ^= 2 等价于 C = C ^ 2
|= 按位或赋值操作符。 如:C |= 2 等价于 C = C | 2
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 3;
int c = a;
System.out.println(c); // 10
a += b
System.out.println(a); // 13
a = 10;
a -= b;
System.out.println(a); // 7
a = 10;
a *= b;
System.out.println(a); // 30
a = 10;
a /= b;
System.out.println(a); // 3
a = 10;
a %= b;
System.out.println(a); // 1 (10除以3余1)
a = 5; // 二进制 0101
a <<= 2; // 左移2位,0101 -> 10100 = 20
System.out.println(a); // 20
a = 20; // 二进制 10100
a >>= 2; // 右移2位,10100 -> 101 = 5
System.out.println(a); // 5
a = 6; // 二进制 0110
a &= 3; // 位与,0110 & 0011 = 0010 = 2
System.out.println("a &= 3 -> " + a); // 2
a = 6; // 二进制 0110
a ^= 3; // 位异或,0110 ^ 0011 = 0101 = 5
System.out.println("a ^= 3 -> " + a); // 5
a = 6; // 二进制 0110
a |= 3; // 位或,0110 | 0011 = 0111 = 7
System.out.println("a |= 3 -> " + a); // 7
}
}
三元运算符(条件运算符)
三元运算符有 3 个操作数,用于根据判断布尔表达式的值,来决定哪个值应该赋值给变量。布尔表达式的值为真则将 : 左侧的值赋给变量,为假则将 : 右侧的值赋给变量。
int a = 1;
int b = 2;
System.out.println(a < b ? 1 : 2);
字符运算符
字符类型可以进行加减运算。
char a = 'a';
char b = 'a';
System.out.println(++a); // b
System.out.println(b + 1); // 98
注意: 两个 char 型运算时,自动转换为 int 型;当 char 与别的类型运算时,也会先自动转换为 int 型的,再做其它类型的自动转换。
逗号运算符
逗号运算符一般用来隔离几个数值。如数组中的每个元素都是逗号分隔。
转型运算符
转型运算符用于将一种类型的对象或数据,强制转换为另一种类型的数据。格式是在需要转换类型的数据前加 ”()“,括号内加入需要转化的数据类型。
int a = (int)11.11 + (int)22.22
System.out.println(a);
运算符优先级
运算符的优先级决定一个多运算符的表达式中,哪个运算符优先级执行。
运算符的优先级:
| 优先级 | 运算符 | 结合性 | ||
|---|---|---|---|---|
| 1 | ( ) [ ] . | 从左到右 | ||
| 2 | ! ~ ++ -- | 从右到左 | ||
| 3 | * / % | 从左到右 | ||
| 4 | + - | 从左到右 | ||
| 5 | << >> >>> | 从左到右 | ||
| 6 | < <= > >= instanceof | 从左到右 | ||
| 7 | == != | 从左到右 | ||
| 8 | & | 从左到右 | ||
| 9 | ^ | 从左到右 | ||
| 10 | \ | 从左到右 | ||
| 11 | && | 从左到右 | ||
| 12 | \ | \ | 从左到右 | |
| 13 | ? : | 从左到右 | ||
| 14 | = += -= *= /= %= &= \ | = ^= ~= <<= >>= >>>= | 从右到左 | |
| 15 | , | 从右到左 |
注意: 级别为 1 的优先级最高,级别 15 的优先级最低。
基本的优先级:
- 指针最优,单目运算优于双目运算
- 先乘除(模),后加减
- 先算术运算,后移位运算,最后位运算。如:1 << 3 + 2 & 7 等价于 (1 << (3 + 2)) & 7.
- 逻辑运算最后计算。
流程控制
if-else 条件语句
if(布尔表达式) {
目的一
}
if (布尔表达式1) {
目的一
} else {
目的二
}
if (布尔表达式1) {
目的一
} else if (布尔表达式2) {
目的二
} else{
目的三
}
switch 语句
switch 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
switch(expression){
case value1 :
语句
break;
case value2 :
语句
break;
default :
语句
}
for 循环语句
for(初始化; 布尔表达式; 更新) {
循环内容
}
for(声明语句 : 表达式) {
循环内容
}
int [] numbers = {10, 20, 30, 40, 50};
for(int n : numbers ){
System.out.print(n);
System.out.print(", ");
}
while 循环语句
while( 布尔表达式 ) {
循环内容
}
do…while 循环语句
while 语句,如果不满足条件,则不能进入循环。
do…while 循环不同:至少会执行一次。
do {
循环内容
} while (布尔表达式);
break 关键字
break 主要用在循环语句或者 switch 语句中,用来跳出整个语句块。
break 跳出最里层的循环,并且继续执行该循环下面的语句。
continue 关键字
continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。
在 for 循环中,continue 语句使程序立即跳转到更新语句。
在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
return 关键字
跳转当前执行程序。
异常处理
异常处理是程序运行时,发生的不被期望的事件。它阻止了程序按照程序员的预期正常执行。
异常发生的原因有很多,通常包含以下几大类:用户输入了非法数据、要打开的文件不存在、网络通信时连接中断,或者 JVM 内存溢出。
三种异常类型:
- 错误: 错误不是异常,而是脱离程序员控制的问题。如:当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
运行时异常: 是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
检查性异常: 是用户错误或问题引起的异常,这是程序员无法预见的。如:要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
java.lang.Object
└── java.lang.Throwable (所有错误和异常的超类)
├── java.lang.Error (严重错误,程序无法处理)
│ ├── VirtualMachineError (虚拟机错误)
│ │ ├── StackOverflowError (栈溢出错误)
│ │ └── OutOfMemoryError (内存溢出错误)
│ ├── AssertionError (断言错误)
│ ├── NoClassDefFoundError (类定义未找到错误)
│ ├── LinkageError (链接错误)
│ └── ... 其他 Error 子类
│
└── java.lang.Exception (异常,程序可以处理)
├── RuntimeException (非检查性异常 / 非受检异常)
│ ├── ArithmeticException (算术异常,如:整数除以0)
│ ├── NullPointerException (空指针异常)
│ ├── ArrayIndexOutOfBoundsException (数组索引越界异常)
│ ├── ClassCastException (类转换异常)
│ ├── IllegalArgumentException (非法参数异常)
│ ├── NumberFormatException (数字格式异常,如:字符串转数字失败)
│ ├── IndexOutOfBoundsException (索引越界异常)
│ ├── SecurityException (安全异常)
│ └── ... 其他 RuntimeException 子类
│
└── 其他所有 Exception 的子类(这些都是检查性异常)
├── IOException (输入输出异常)
│ ├── FileNotFoundException (文件未找到异常)
│ ├── EOFException (文件结束异常)
│ └── ... 其他 IOException 子类
├── ClassNotFoundException (类未找到异常)
├── CloneNotSupportedException (不支持克隆异常)
├── IllegalAccessException (非法访问异常)
├── InstantiationException (实例化异常)
├── InterruptedException (中断异常,如:线程被中断)
├── NoSuchFieldException (字段不存在异常)
├── NoSuchMethodException (方法不存在异常)
├── SQLException (SQL 异常)
└── ... 其他检查性异常子类
try 捕获异常
使用 try 和 catch 关键字可以捕获异常。
try {
程序代码
} catch(ExceptionName e1) {
catch 块
}
throws/throw 关键字
如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。
也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
public class className {
public void getValue(double value) throws RemoteException {
// 方法实现
if (条件) {
throw new RemoteException();
}
}
}
finally 关键字
finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。
try {
程序代码
} catch (异常类型1 异常的变量名1){
程序代码
} catch (异常类型2 异常的变量名2){
程序代码
} finally {
程序代码
}
Object 类
Object 类是 Java 中所有类的根父类(顶级父类)。
在 Java 中,每个类都直接或间接地继承自 Object 类,包括数组类型和自定义类。如果一个类没有使用 extends 关键字显式指定父类,那么它默认继承 Object 类。
Object 类的常用方法:
- toString():返回对象的字符串表示,通常需要重写。
- equals(Object obj):判断两个对象是否相等,比较两个对象的内存地址,通常需要重写。
- hashCode():返回对象的内存地址转换成的整数(不一定绝对唯一),通常需要重写(重写
equals()时必须重写)。 - getClass():返回对象的运行时类信息(Class 对象),可用于反射,不需要重写(final 方法)。
- clone():创建并返回对象的副本,根据需要实现 Cloneable 接口。
- wait() / wait(long timeout) / wait(long timeout, int nanos):线程等待,不需要重写(用于多线程)。
- notify() / notifyAll():唤醒等待的线程,不需要重写(用于多线程)。
// Object 类默认的 toString() 实现
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// Object 类默认的 equals() 实现
public boolean equals(Object obj) {
return (this == obj);
}
String s = "Hello";
Class<?> c = s.getClass();
System.out.println(c.getName());
System.out.println(c.getSimpleName());
System.out.println(c.isInterface());
wait() / notify() / notifyAll() 用于多线程环境中的线程协作,必须在 synchronized 代码块或方法中调用:
public class WaitNotifyApplication {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 线程1:等待线程
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等待中...");
// 释放当前线程持有的锁,让当前线程进入等待状态(WAITING)
// 等待其他线程调用 notify()/notifyAll() 唤醒
lock.wait();
System.out.println("被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 线程2:唤醒线程
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
System.out.println("准备唤醒...");
// 随机唤醒一个等待线程
// 被唤醒的线程必须等待当前线程释放锁后才能执行
lock.notify();
System.out.println("未唤醒...");
// 执行完会自动释放锁
}
});
// 启动线程
waitingThread.start();
Thread.sleep(1000);
notifyingThread.start();
}
}
Number 类
Java 的的基本数据类型有 8 种:byte、int、short、long、double、float、boolean、char。
对应的包装类型也有 8 种:Byte、Integer、Short、Long、Double、Float、Boolean、Character。
包装类型都是用 final 声明了,不可以被继承重写。在实际情况中,编译器会自动的将基本数据类型装箱成对象类型,或者将对象类型拆箱成基本数据类型。
Number 类是 java.lang 包下的一个抽象类,提供了将包装类型拆箱成基本类型的方法。
Number 类的直接子类有:
ByteShortIntegerLongFloatDouble
还有 BigDecimal、BigInteger 等,但这些属于 java.math 包,也是 Number 的子类。
Character 和 Boolean 继承自 Object 类,与 Number 是平级关系。
Number 类的方法:
- xxxValue(): 将 Number 对象转换为 xxx 数据类型的值并返回。
- compareTo(): 判断两个对象的大小关系,返回
int类型(负数、0 或正数)。来自Comparable接口。 - equals(): 判断两个对象是否相等,返回
boolean类型。来自Object类。 - valueOf(): 返回一个指定数据类型的包装类对象,是静态方法,直接通过类名调用。
- toString(): 返回对象的字符串表示形式,是静态方法,直接通过类名调用。
- parseXxx(): 将字符串解析为 Xxx 类型,是静态方法,直接通过类名调用。
Byte a1 = 127;
Integer a2 = 2000;
Short a3 = 32767;
Long a4 = 400000L;
Double a5 = 5.01;
Float a6 = 6.02F;
Character a7 = '7';
Boolean a8 = true;
System.out.println("\nxxxValue:");
System.out.println(a5.byteValue());
System.out.println(a1.intValue());
System.out.println(a1.shortValue());
System.out.println(a1.longValue());
System.out.println(a1.doubleValue());
System.out.println(a1.floatValue());
System.out.println("\ncompareTo:");
System.out.println(a1.compareTo((byte) 8));
System.out.println(a2.compareTo(8));
System.out.println(a3.compareTo((short) 8));
System.out.println(a4.compareTo(8L));
System.out.println(a5.compareTo(8.0));
System.out.println(a6.compareTo(8.0F));
System.out.println(a7.compareTo('8'));
System.out.println(a8.compareTo(true));
System.out.println("\nequals:");
System.out.println(a1.equals(9));
System.out.println(a2.equals(9));
System.out.println(a3.equals(9));
System.out.println(a4.equals(9));
System.out.println(a5.equals(9));
System.out.println(a6.equals(9));
System.out.println(a7.equals('9'));
System.out.println(a8.equals(true));
System.out.println("\nvalueOf:");
System.out.println(Byte.valueOf("127"));
System.out.println(Integer.valueOf("2000"));
System.out.println(Short.valueOf("32767"));
System.out.println(Long.valueOf("400000"));
System.out.println(Double.valueOf("5.01"));
System.out.println(Float.valueOf("6.02"));
System.out.println(Character.valueOf('7'));
System.out.println(Boolean.valueOf(true));
System.out.println("\ntoString:");
System.out.println(a1.toString());
System.out.println(a2.toString());
System.out.println(a3.toString());
System.out.println(a4.toString());
System.out.println(a5.toString());
System.out.println(a6.toString());
System.out.println(a7.toString());
System.out.println(a8.toString());
System.out.println("\nparseXxx:");
System.out.println(Byte.parseByte("9"));
System.out.println(Integer.parseInt("9"));
System.out.println(Short.parseShort("9"));
System.out.println(Long.parseLong("9"));
System.out.println(Double.parseDouble("9"));
System.out.println(Float.parseFloat("9"));
System.out.println(Boolean.parseBoolean("9"));
Math 类
Java 的 Math 包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。
Math 的方法都被定义为 static 形式,直接通过类名调用静态方法。
Math 类的方法:
- abs(): 返回参数的绝对值。
- ceil(): 返回大于等于( >= )给定参数的的最小整数,类型为双精度浮点型。
- floor(): 返回小于等于(<=)给定参数的最大整数 。
- rint(): 返回与参数最接近的整数。返回类型为double。
- round(): 返回一个最接近的int、long型值。
- min(): 返回两个参数中的最小值。
- max(): 返回两个参数中的最大值。
- exp(): 返回自然数底数e的参数次方。
- log(): 返回参数的自然数底数的对数值。
- pow(): 返回第一个参数的第二个参数次方。
- sqrt(): 求参数的算术平方根。
- sin(): 求指定double类型参数的正弦值。
- cos(): 求指定double类型参数的余弦值。
- tan(): 求指定double类型参数的正切值。
- asin(): 求指定double类型参数的反正弦值。
- acos(): 求指定double类型参数的反余弦值。
- atan(): 求指定double类型参数的反正切值。
- atan2(): 将笛卡尔坐标转换为极坐标,并返回极坐标的角度值。
- toDegrees(): 将参数转化为角度。
- toRadians(): 将角度转换为弧度。
- random(): 返回一个随机数。
Integer a = 100;
Short b = 5;
double abs = Math.abs(-5.5);
double max = Math.max(10, 20);
System.out.println(Math.random());
Character 类
Character 类是 Java 中用于包装 char 基本类型的包装类,位于 java.lang 包下。它提供了一系列静态方法来处理字符,如判断字符类型、大小写转换等。
char c = 'a';
char u = '\u039A';
char[] cArray = { 'a', 'b', 'c', 'd', 'e' };
Character c = new Character('a');
Character 类的方法:
- isLetter(): 是否是一个字母。
- isDigit(): 是否是一个数字字符。
- isWhitespace(): 是否一个空格。
- isUpperCase(): 是否是大写字母。
- isLowerCase(): 是否是小写字母。
- toUpperCase(): 指定字母的大写形式。
- toLowerCase(): 指定字母的小写形式。
- toString(): 返回字符的字符串形式。
Character c = new Character('a');
System.out.println(Character.isLetter(c));
System.out.println(Character.isDigit(c));
System.out.println(Character.isWhitespace(c));
System.out.println(Character.isUpperCase(c));
System.out.println(Character.isLowerCase(c));
System.out.println(Character.toUpperCase(c));
System.out.println(Character.toLowerCase(c));
System.out.println(Character.toString(c));
String 类
String 类是 Java 中最常用的类之一,用于表示和操作字符串(字符序列)。它位于 java.lang 包下,在 Java 程序中随处可见。
String 类被初始化后,长度是不变的,并且内容也不变。如果要改变它的值,就会产生一个新的字符串。
运行原理: 先创建一个字符串对象 str,并在内存中申请一段空间。如果要改变它,只能放弃原来的空间,重新申请能够容纳新字符串的内存空间,再将字符串放到内存中。
String a = "Hello world!";
String b = new String("Hello world!");
char[] cArray = { 'h', 'e', 'l', 'l', 'o', '.'};
String c = new String(cArray);
备注: String 类有 11 种构造方法,这些方法提供不同的参数来初始化字符串。
String 类的方法:
- indexOf():返回指定字符或字符串第一次出现的索引,如果没有,则返回 -1。
lastIndexOf() :返回指定字符或字符串最后一次出现的索引,如果没有,则返回 -1。
charAt():用于返回指定索引处的字符。索引范围为从 0 ~ length() - 1。
compareTo():用于字符串与对象进行比较,或者按字典顺序比较两个字符串。
compareToIgnoreCase():与 compareTo() 相同,但忽略大小写。
- concat():用于将指定的字符串参数连接到字符串上。
- contentEquals():用于将字符串与指定的 StringBuffer 比较。
- endsWith():用于测试字符串是否以指定的后缀结束。
- equals():用于将字符串与指定的对象比较。
- equalsIgnoreCase():与 equals() 方法相同,但忽略大小写。
- getBytes():使用平台默认或指定的字符集,将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
- getChars():将字符从字符串复制到目标字符数组。
- hashCode():用于返回字符串的哈希码。
- length():用于返回字符串的长度。长度等于字符串中 16 位 Unicode 代码单元的数量。
- matches():用于检测字符串是否匹配给定的正则表达式。
- regionMatches():用于检测两个字符串在一个区域内是否相等。
- replace():用于将所有出现的老字符替换为新字符串,并返回替换后的新字符串。
- replaceAll():用于将所有匹配给定的正则表达式的字符替换,并返回替换后的新字符串。
- replaceFirst():用于将所有第一次匹配给定的正则表达式的字符替换,并返回替换后的新字符串。
- split():根据匹配给定的正则表达式来拆分字符串。
- startsWith():用于检测字符串是否以指定的前缀开始。
- subSequence():返回一个新的字符序列,它是此序列的一个子序列。
- substring():返回字符串的子字符串。
- toCharArray():将字符串转换为字符数组。
- toLowerCase():将字符串转换为小写。
- toUpperCase():将字符串小写字符转换为大写。
- toString():返回此对象本身(它已经是一个字符串)。
- trim():用于删除字符串的头尾空白符。
- copyValueOf():返回指定数组中表示该字符序列的字符串。
- intern():返回字符串对象的规范化表示形式。
- valueOf():返回参数的删除头尾空白符的字符串表示形式。
String a = "Hello,李兆,lizhao!";
String b = "李兆";
String c = "Hello,李兆,lizhao!";
System.out.println(a.indexOf('l'));
System.out.println(a.indexOf('l', 6 ));
System.out.println(a.indexOf(b));
System.out.println(a.indexOf(b, 15 ));
System.out.println(a.lastIndexOf('l'));
System.out.println(a.lastIndexOf('l', 6 ));
System.out.println(a.lastIndexOf(b));
System.out.println(a.lastIndexOf(b, 15 ));
System.out.println(a.charAt(7));
System.out.println(a.compareTo(b));
System.out.println(b.compareTo(a));
System.out.println(a.compareTo(c));
StringBuilder、StringBuffer 类
这两个类都是用来创建和操作可变字符串的,和 String 的区别是:String 不可变,StringBuilder/StringBuffer 可变。
StringBuilder 提供了一个可变的字符串对象,允许在不创建新对象的情况下对字符串进行增删改查操作。
StringBuffer 的功能和 StringBuilder 基本相同,但所有公共方法都使用了 synchronized 关键字进行同步,保证了在多线程环境下的安全性。
StringBuilder 有速度优势(因为它不是线程安全的,没有同步开销),多数情况下建议使用 StringBuilder 类,只有在要求多线程安全的情况下使用 StringBuffer 类。
备注: StringBuilder 和StringBuffer 的原理、功能和方法基本相同。
StringBuffer 类的方法:
- capacity():返回当前可容纳的字符数。
- ensureCapacity():确保容量至少等于指定的最小值。
- append():将参数的字符串表示形式追加到当前字符。
- reverse():将字符序串反转。
- delete():移除字符串的子字符串的字符。
- insert():将第二个参数的字符串表示形式插入到当前字符串。
- replace():使用给定字符替换字符串的子字符串。
StringBuilder a = new StringBuilder ("Hello,李兆!");
StringBuilder b = new StringBuilder ("Lizhao!");
System.out.println(a.capacity());
a.ensureCapacity(100);
System.out.println(a.capacity());
System.out.println(a.append(b));
System.out.println(a.reverse());
System.out.println(a.delete(6, 8));
System.out.println(a.append(true));
System.out.println(a.replace(6, 8, "Lizhao"));
日期时间类
Date 类
Date 类用来表示一个具体的时间点,内部存储的是从 1970 年 1 月 1 日 00:00:00 GMT 至今的毫秒数。
java.util 包提供了 Date 类来封装当前的日期和时间。
Date();
Date(long millisec); // millisec 是从 1970年1月1日 起的毫秒数
Date 类的方法:
- after(): 若调用此方法的 Date 对象在指定日期之后返回 true,否则返回 false。
- before(): 若调用此方法的 Date 对象在指定日期之前返回 true,否则返回 false。
- clone(): 返回此对象的副本。
- compareTo(): 比较当调用此方法的 Date 对象和指定日期。两者相等时候返回 0;调用对象在指定日期之前则返回负数;调用对象在指定日期之后则返回正数。
- equals(e): 若调用此方法的 Date 对象和指定日期相等时候返回 true,否则返回 false。
- getTime(): 返回自 1970年1月1日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
- hashCode(): 返回此对象的哈希码值。
- setTime(): 用自 1970年1月1日 00:00:00 GMT 以后时间毫秒数设置时间和日期。
- toString(): 转换为 String 表示形式,并返回该字符串。
SimpleDateFormat 类用于在 Date 对象和字符串之间进行转换,按照指定的格式进行格式化和解析。SimpleDateFormat 类时间模式字符串用来指定时间格式:
G 纪元标记
y 四位年份
M 月份
d 一个月的日期
h A.M./P.M.(1~12)格式小时
H 一天中的小时(0~23)
m 分钟数
s 秒数
S 毫秒数
E 星期几
D 一年中的日子
F 一个月中第几周的周几
w 一年中第几周
W 一个月中第几周
a A.M./P.M. 标记
k 一天中的小时(1~24)
K A.M./P.M. (0~11)格式小时
z 时区
' 文字定界符
" 单引号
import java.util.Date;
import java.text.SimpleDateFormat;
public class DateTimeApplication {
public static void main(String[] args) {
// Date 对象输出为日期时间字符串
Date date = new Date();
SimpleDateFormat format1 = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss a zzz");
SimpleDateFormat format2 = new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒 E 中国 G");
SimpleDateFormat format3 = new SimpleDateFormat("y年M月d日 h时m分s秒S毫秒 D 中国 G");
System.out.println(format1.format(date));
System.out.println(format2.format(date));
System.out.println(format3.format(date));
// 日期时间字符串解析为 Date 对象
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
try {
Date date2 = format.parse("2024-05-31 22:08:55");
System.out.println(format.format(date2));
} catch (Exception e) {
System.out.println("格式错误");
}
}
}
注意: 在 Java 8 之前,Date 和 Calendar 是处理日期时间的唯一选择,虽然它们有很多缺陷,但不得不用。在 Java 8 之后,官方推出了全新的 java.time 包(LocalDate、LocalDateTime 等),彻底解决了旧 API 的问题。从此,新项目开始逐步淘汰 Date。
Calendar 类
Calendar 是一个抽象类,提供了日期时间的计算和字段操作功能,弥补了 Date 在日期运算上的不足。
Calendar 类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用 getInstance 方法创建即可。
import java.util.*;
Calendar c = Calendar.getInstance();
Calendar 类对象字段类型:
Calendar.YEAR 年份
Calendar.MONTH 月份
Calendar.DATE 日期
Calendar.DAY_OF_MONTH 日期,和上面的字段意义完全相同
Calendar.HOUR 12小时制的小时
Calendar.HOUR_OF_DAY 24小时制的小时
Calendar.MINUTE 分钟
Calendar.SECOND 秒
Calendar.DAY_OF_WEEK 星期几
Calendar 类的方法:
- set():设置 Calendar 对象的日期时间值。有多种重载形式。
- add():对某个字段进行增加或减少操作。在当前值的基础上进行变化,并且会自动处理进位问题(比如:加30天会自动跨月)。
- get():从 Calendar 对象中获取指定字段的值,比如获取年、月、日、时、分、秒等。
import java.util.Calendar;
public class DateTimeApplication {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
c.set(2026, 3, 21); // 月份从 0 开始,此处 3 表示 4 月
c.set(Calendar.DATE, 15);
c.add(Calendar.DATE, 10);
System.out.println(c.get(Calendar.DATE));
System.out.println(c.get(Calendar.MONTH));
}
}
GregorianCalendar 类
GregorianCalendar 是 Calendar 的具体实现类,实现了公历(格里高利历)日历系统。
Calendar 的 getInstance() 方法返回一个默认用当前的语言环境和时区初始化的 GregorianCalendar 对象。GregorianCalendar 定义了两个字段:AD 和 BC。这些代表公历定义的两个时代。
GregorianCalendar 特有的方法:
- isLeapYear():判断闰年
- getGregorianChange():获取历法切换日期
- setGregorianChange():设置历法切换日期
GregorianCalendar()
GregorianCalendar(int year, int month, int date)
GregorianCalendar(int year, int month, int date, int hour, int minute)
GregorianCalendar(int year, int month, int date, int hour, int minute, int second)
GregorianCalendar(Locale aLocale)
GregorianCalendar(TimeZone zone)
GregorianCalendar(TimeZone zone, Locale aLocale)
import java.util.Locale;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class DateTimeApplication {
public static void main(String[] args) {
GregorianCalendar g = new GregorianCalendar(Locale.CHINESE);
System.out.println(g.get(Calendar.MONTH));
System.out.println(g.get(Calendar.DATE));
System.out.println(g.get(Calendar.YEAR));
System.out.println(g.get(Calendar.HOUR));
System.out.println(g.get(Calendar.MINUTE));
System.out.println(g.get(Calendar.SECOND));
}
}
LocalDate/LocalTime/LocalDateTime 类
Java 8 引入了全新的日期时间 API,位于 java.time 包中,解决了旧 API 的所有问题:线程安全、不可变、API 清晰、支持时区:
LocalDate、LocalTime、LocalDateTime:日期、时间、日期+时间。DateTimeFormatter:日期格式化,适用于LocalDate、LocalTime和LocalDateTime。Period:按年月日计算两个日期之间的间隔,适用于LocalDate。Duration:按时分秒纳秒计算两个时间之间的间隔,适用于LocalTime、LocalDateTime。
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.Period;
import java.time.Duration;
public class DateTimeApplication {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2025, 12, 25);
System.out.println("【LocalDate 日期】");
System.out.println(" 今天日期:" + today);
System.out.println(" 生日:" + birthday);
System.out.println(" 年份:" + birthday.getYear());
System.out.println(" 月份:" + birthday.getMonthValue());
System.out.println(" 几号:" + birthday.getDayOfMonth());
LocalTime now = LocalTime.now();
LocalTime meeting = LocalTime.of(14, 30, 0);
System.out.println("\n【LocalTime 时间】");
System.out.println(" 当前时间:" + now);
System.out.println(" 会议时间:" + meeting);
System.out.println(" 小时:" + meeting.getHour());
System.out.println(" 分钟:" + meeting.getMinute());
LocalDateTime current = LocalDateTime.now();
LocalDateTime christmas = LocalDateTime.of(2025, 12, 25, 10, 0, 0);
System.out.println("\n【LocalDateTime 日期时间】");
System.out.println(" 当前日期时间:" + current);
System.out.println(" 圣诞节:" + christmas);
System.out.println(" 提取日期:" + christmas.toLocalDate());
System.out.println(" 提取时间:" + christmas.toLocalTime());
LocalDateTime now2 = LocalDateTime.now();
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy/MM/dd EEEE");
System.out.println("\n【DateTimeFormatter 格式化与解析】");
System.out.println(" ISO格式:" + now2.format(DateTimeFormatter.ISO_DATE_TIME));
System.out.println(" 自定义格式1:" + now2.format(formatter1));
System.out.println(" 自定义格式2:" + now2.format(formatter2));
System.out.println(" 自定义格式3:" + now2.format(formatter3));
LocalDate birthday2 = LocalDate.of(1995, 8, 20);
LocalDate today2 = LocalDate.now();
Period period = Period.between(birthday2, today2);
System.out.println("\n【Period 日期间隔】");
System.out.println(" 生日:" + birthday2);
System.out.println(" 今天:" + today2);
System.out.println("\n【年龄计算】");
System.out.println(" 年龄:" + period.getYears() + "岁");
System.out.println(" 详细:" + period.getYears() + "年" + period.getMonths() + "个月" + period.getDays() + "天");
LocalTime start = LocalTime.of(9, 0, 0);
LocalTime end = LocalTime.of(17, 30, 45);
Duration duration = Duration.between(start, end);
System.out.println("\n【Duration 时间间隔】");
System.out.println(" 开始时间:" + start);
System.out.println(" 结束时间:" + end);
System.out.println(" 间隔小时:" + duration.toHours());
System.out.println(" 间隔分钟:" + duration.toMinutes());
System.out.println(" 间隔秒数:" + duration.getSeconds());
}
}
正则表达式
正则表达式定义了字符串的模式。
正则表达式可以用来搜索、编辑或处理文本。
正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
java.util.regex 包主要包括以下三个类:
- Pattern 类: 一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,必须调用其公共静态编译方法。
- Matcher 类:对输入字符串进行解释和匹配操作的引擎。Matcher 类也没有公共构造方法,需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
- PatternSyntaxException:一个非强制异常类,它表示一个正则表达式模式中的语法错误。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexApplication {
public static void main(String[] args) {
String a = "Hello,李兆!";
String b = "(.*)(李兆)(.*)";
String c = "李兆";
Pattern p = Pattern.compile(b);
Matcher m = p.matcher(a);
m.matches();
System.out.println(m.group(2));
p = Pattern.compile(c);
m = p.matcher(a);
m.matches();
a = m.replaceAll("Lizhao");
System.out.println(a);
}
}
流(Stream)、文件(File)和IO
Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
控制台输入
Java 的控制台输入由 System.in 完成。
从控制台读取多字符输入,从 BufferedReader 对象读取一个字符要使用 read() 方法:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ReadWriteApplication {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("输入字符(按下 'q' 键退出):");
char c;
do {
c = (char) br.read();
System.out.println(c);
} while (c != 'q');
} catch (Exception e) {
System.out.println("异常");
}
}
}
从控制台读取字符串,从标准输入读取一个字符串需要使用 BufferedReader 的 readLine() 方法:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ReadWriteApplication {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("输入行(输入 end 退出):");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("end"));
} catch (Exception e) {
System.out.println("异常");
}
}
}
控制台输出
控制台的输出由 print() 和 println() 完成。这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。
PrintStream 继承了 OutputStream 类,并且实现了方法 write()。所以,write() 也可以用来往控制台写操作。
public class Main {
public static void main(String[] args) {
System.out.println(args[0] + " " + args[1]);
System.out.println(System.getenv("DB_URL"));
System.out.println(System.getenv("DB_PASSWORD"));
double[] myList = { 1.1, 2.2, 3.3, 4.4 };
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i] + " ");
}
System.out.write(65);
System.out.write(100);
// println() 方法内部会自动 flush,而 write() 不会,因此需要手动调用 flush()
System.out.flush();
}
}
注意:println() 可支持字符串、基本类型、对象等,而write() 只能接受 int 或 byte[]。
读文件
FileInputStream 是 Java 中用于从文件系统读取数据的字节输入流,属于 java.io 包。它主要用于以字节为单位从文件中读取原始数据,如图片、音频、视频或文本文件的二进制内容,是 Java I/O 体系中非常基础且重要的类。
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadWriteApplication {
public static void main(String[] args) throws IOException {
String fileName = "hello";
String outStr = "";
InputStream is = new FileInputStream(fileName);
int size = is.available();
for (int i = 0; i < size; i++) {
char c = (char) is.read();
outStr = outStr + Character.toString(c);
}
System.out.println(outStr);
is.close();
}
}
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.IOException;
public class ReadWriteApplication {
public static void main(String[] args) throws IOException {
String fileName = "hello";
InputStream is = new FileInputStream(fileName);
InputStreamReader reader = new InputStreamReader(is, "UTF-8");
while (reader.ready()) {
System.out.println((char) reader.read() + " ");
}
reader.close();
is.close();
}
}
写文件
FileOutputStream 是 Java 中用于将数据写入文件的字节输出流,属于 java.io 包。它主要用于将原始的字节数据(如图片、音频、视频或文本文件的二进制内容)写入到文件系统中。
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
public class ReadWriteApplication {
public static void main(String[] args) throws IOException {
String fileName = "hello";
String inStr = "Hello, Lizhao!222";
OutputStream os = new FileOutputStream(fileName);
for (int i = 0; i < inStr.length(); i++) {
int b = (int) inStr.charAt(i);
os.write(b);
}
os.close();
}
}
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.IOException;
public class ReadWriteApplication {
public static void main(String[] args) throws IOException {
String fileName = "hello";
OutputStream os = new FileOutputStream(fileName);
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
writer.append("Hello");
writer.append(",");
writer.append("Lizhao");
writer.append("!");
writer.close();
os.close();
}
}
目录管理
创建文件夹,有两个方法:
- mkdir():创建一个文件夹。失败表明(不会抛出报错信息)指定的路径已经存在,或者父目录还不存在。
- mkdirs():创建一个文件夹和它的所有父目录。
import java.io.File;
public class ReadWriteApplication {
public static void main(String[] args) {
String dirname1 = "tempDir1";
String dirname2 = "tempDir2/lizhao/hello";
File dir1 = new File(dirname1);
File dir2 = new File(dirname2);
dir1.mkdir();
dir2.mkdirs();
}
}
读取目录,其实就是一个 File 对象,它包含其他文件和文件夹:
import java.io.File;
public class ReadWriteApplication {
public static void main(String[] args) {
String dirname = "./";
File dir = new File(dirname);
if (dir.isDirectory()) {
String strArray[] = dir.list();
for (int i=0; i < strArray.length; i++) {
File f = new File(dirname + "/" + strArray[i]);
System.out.println(strArray[i] + "是一个" + (f.isDirectory() ? "目录" : "文件"));
}
} else {
System.out.println(dirname + "不是一个目录");
}
}
}
删除目录或文件,用 java.io.File.delete() 方法:
import java.io.File;
public class ReadWriteApplication {
public static void main(String[] args) {
String dirname = "tempDir/subDir";
File dir = new File(dirname);
File[] files = dir.listFiles();
if (files == null) {
dir.mkdirs();
}
dir.delete();
}
}
注意: 当 file 是目录,且目录下有其他子目录或文件时,目录不能删除。
数组
数组是 Java 中最基本的数据存储结构,它是一段连续的内存空间,用于存储固定数量的同一类型数据。数组的长度在创建后不可改变,可以存储基本数据类型(如 int、double、char 等)以及引用类型,并且通过索引访问元素的速度非常快,性能极高。
数组的功能相对基础,仅提供了通过下标进行读写的操作,而像添加、删除、查找等常见功能都需要开发者手动实现。
总的来说:数组适合用于长度固定、追求极致性能或需要存储基本数据类型的简单场景。
声明数组变量:
dataType[] arrayRefVar; // 首选的方法
dataType arrayRefVar[]; // 效果相同,但不是首选方法
创建数组:
Java 语言使用 new 操作符来创建数组。
dataType[] arrayVar = new dataType[arraySize];
dataType[] arrayVar = {value0, value1, ..., valuek};
处理数组:
数组的元素类型和数组的大小都是确定的,所以通常使用 for 或者 foreach 循环处理数组。
double[] myList = { 1.1, 2.2, 3.3, 4.4 };
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i] + " ");
}
for (double ele: myList) {
System.out.println(ele);
}
Collection 接口
Collection 是 Java 集合框架中的根接口,它定义了所有单列集合(存储一个个元素的集合)的通用行为规范。它的最大特点是长度可变,可以根据需要动态扩容,无需预先指定容量。
Collection 接口提供了丰富的方法来操作数据。结合泛型使用,Collection 可以在编译期进行严格的类型检查,避免类型转换异常,提高了代码的安全性和可读性。
注意:由于方法封装和动态扩容,Collection 在性能上比数组会有一些额外的性能开销。
注意:Collection 只能存储引用类型,无法直接存储基本数据类型。
Collection 接口:
- List 接口:有序、可重复的集合。
- ArrayList:基于数组实现,查询快。
- LinkedList:基于链表实现,增删快。
- Vector:线程安全的数组实现。
- Set 接口:无序、不可重复的集合。
- HashSet:哈希表实现,最快。
- LinkedHashSet:哈希表加链表,保持插入顺序。
- TreeSet:基于红黑树实现,可以排序。
- Queue 接口:队列
- LinkedList:也可以作为队列使用。
- PriorityQueue:优先级队列。
Collection 接口的核心方法:
boolean add(E element):向集合中添加一个元素。如果集合因为添加而改变,返回 true。boolean addAll(Collection c):将另一个集合中的所有元素添加到当前集合中。boolean remove(Object obj):从集合中移除指定的元素。如果元素存在并移除成功,返回 true。boolean removeAll(Collection c):移除当前集合中存在于另一个集合中的所有元素(差集操作)。boolean retainAll(Collection c):只保留当前集合中同时存在于另一个集合中的元素(交集操作)。void clear():移除集合中的所有元素。int size():返回集合中元素的数量。boolean isEmpty():判断集合是否为空。boolean contains(Object obj):判断集合是否包含指定的元素。boolean containsAll(Collection c):判断集合是否包含另一个集合中的所有元素。Iterator iterator():返回一个迭代器,用于遍历集合中的所有元素。Object[] toArray():将集合转换成一个 Object 类型的数组。T[] toArray(T[] a):将集合转换成指定类型的数组。
Collection(接口)
├── AbstractCollection(抽象类)(骨架实现)
│ ├── List(接口)(有序,可重复,有索引)
│ │ ├── AbstractList(抽象类)
│ │ │ ├── ArrayList(类)(动态数组,查询快)
│ │ │ ├── LinkedList(类)(双向链表,增删快)
│ │ │ │ └── 同时实现 Deque(双端队列)接口
│ │ │ └── Vector(类)(线程安全,遗留类)
│ │ │ └── Stack(类)(栈,后进先出,不推荐)
│ │ └── AbstractSequentialList(抽象类)
│ │ └── LinkedList(类)(双向链表)
│ ├── Set(接口)(无序,不可重复)
│ │ ├── AbstractSet(抽象类)
│ │ │ ├── HashSet(类)(哈希表,无序,最快)
│ │ │ │ └── LinkedHashSet(类)(哈希表+链表,插入顺序)
│ │ │ ├── EnumSet(抽象类)(枚举专用)
│ │ │ │ └── RegularEnumSet(类)(JVM内部)
│ │ │ └── TreeSet(类)(红黑树,自动排序)
│ │ └── SortedSet(接口)(排序 Set 接口)
│ │ └── NavigableSet(接口)(导航 Set 接口)
│ │ └── TreeSet(类)(红黑树实现)
│ └── Queue(接口)(队列,先进先出)
│ ├── AbstractQueue(抽象类)
│ │ └── PriorityQueue(类)(优先级队列,堆实现)
│ └── Deque(接口)(双端队列)
│ ├── ArrayDeque(类)(循环数组,高效)
│ └── LinkedList(类)(双向链表)
└── Collections(工具类)(全静态方法,工具类)
注意:Collection是集合的根接口,而Collections是一个操作集合的静态工具类,不要混淆。
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Collection<String> names = new ArrayList<>();
names.add("Hello");
names.add("Lizhao");
names.add("李兆");
System.out.println("\n方法一:增强 for 循环(遍历时不能删除元素):");
for (String name : names) {
System.out.println(name);
}
System.out.println("\n方法二:迭代器 Iterator(遍历时可以删除元素):");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("Lizhao")) {
it.remove();
System.out.println("删除:" + name);
}
}
System.out.println("删除后的集合:" + names);
System.out.println("\n方法三:Lambda 表达式(Java 8+)");
System.out.println("不能删除元素,不能使用 break/return 提前终止:");
names.forEach(name -> System.out.println(name));
names.forEach(System.out::println);
names.forEach(name -> {
if (name.startsWith("Lizhao")) {
System.out.println(name);
}
});
System.out.println("\n方法四:转成数组遍历");
System.out.println("创建新数组,对数组的修改不影响原集合:");
Object[] arr = names.toArray();
for (Object obj : arr) {
String name = (String) obj;
System.out.println(name);
}
System.out.println("\n方法五:普通 for 循环(仅适用于 List)");
System.out.println("Collection 接口没有 get() 方法,只有 List 才有索引:");
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("Lizhao");
list.add("李兆");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
List 接口
List 接口在继承 Collection 接口的基础上,主要扩展了与索引(位置)相关的操作方法。因为 Collection 没有索引的概念,而 List 是有序集合,每个元素都有对应的索引位置。
简单来说:List = Collection + 索引操作。
List 接口在 Collection 接口基础上的扩展方法:**
get(int index):获取指定位置的元素。set(int index, E element):修改指定位置的元素。add(int index, E element):在指定位置插入元素。remove(int index):删除指定位置的元素。indexOf(Object o)、lastIndexOf(Object o):查找元素的位置。subList(int fromIndex, int toIndex):获取子列表视图。listIterator():可以向前向后遍历。
ArrayList、LinkedList 和 Vector 是 List 接口的三个主要实现类,它们都实现了 List 接口的所有方法,但内部实现机制和性能特点不同。
ArrayList 类
ArrayList 基于数组实现,查询快。
获取数据的时间复杂度是 O(1),但是插入、添加、删除数据的时间复杂度是 O(n),开销很大——因为需要重新计算大小或者是更新索引,或者在数组装满的时候要将所有的数据重新装入一个新的数组。
ArrayList 类在 List 接口基础上的扩展方法:
void ensureCapacity(int minCapacity):确保内部数组容量至少为指定值,减少多次扩容的性能开销。void trimToSize():将内部数组容量调整为当前元素个数,释放多余内存。Object clone():返回 ArrayList 的浅拷贝副本。void removeRange(int fromIndex, int toIndex):移除指定范围内的元素。
LinkedList 类
LinkedList 是基于双向链表实现,增删快
每一个元素都和它的前一个和后一个元素链接在一起,只能顺序访问。LinkedList 比 ArrayList 更占内存,它的每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。它的插入、添加、删除操作速度更快,时间复杂度仅为 O(1) ——因为当元素被添加到集合任意位置的时候,只需要更新相应元的两个引用,但是它查找某个元素的时间复杂度是 O(n)——需要从第一个元素顺序数到第 n 个数据。
LinkedList 类在 List 接口基础上的扩展方法:
void addFirst(E e):在列表开头插入元素。void addLast(E e):在列表结尾插入元素。E getFirst():获取第一个元素(不删除)。E getLast():获取最后一个元素(不删除)。E removeFirst():删除并返回第一个元素。E removeLast()- 删除并返回最后一个元素。boolean offer(E e):在队尾添加元素。E poll():删除并返回队首元素。E peek():获取队首元素(不删除)。void push(E e):压入栈顶。E pop():弹出栈顶元素(删除并返回)。Object clone():返回 LinkedList 的浅拷贝副本。
Vector 类
Vector 基于线程安全的数组实现。
Vector 基本用法和 ArrayList 类似,但所有方法都使用了 synchronized 关键字修饰,保证了线程安全。
Vector 类在 List 接口基础上的扩展方法:
int capacity():获取当前内部数组的容量。void ensureCapacity(int minCapacity):确保容量至少为指定值。void trimToSize():将容量调整为当前元素个数。E firstElement():获取第一个元素。E lastElement():获取最后一个元素。Enumeration<E> elements():返回 Enumeration 枚举对象(旧式遍历)。Vector(int initialCapacity):指定初始容量。Vector(int initialCapacity, int capacityIncrement):指定初始容量和每次扩容的增量。Object clone():返回 Vector 的浅拷贝副本。void copyInto(Object[] anArray):将元素复制到指定数组。E elementAt(int index):获取指定索引的元素(类似 get 方法)。void setElementAt(E obj, int index):设置指定索引的元素。void removeElementAt(int index):删除指定索引的元素。void insertElementAt(E obj, int index):在指定索引插入元素。void addElement(E obj):添加元素。boolean removeElement(Object obj):删除指定元素。void removeAllElements():删除所有元。
Stack 类
Stack 类基于 Vector 实现,代表一个后进先出的栈数据结构。Stack 继承自 Vector,在 Vector 的基础上扩展了栈操作相关的方法。
简单来说:Stack = Vector + 栈操作(后进先出)。
Stack 类的核心特点:
- 后进先出:最后压入栈的元素最先被弹出。
- 继承自 Vector:拥有 Vector 的所有方法(线程安全)。
- 线程安全:继承自 Vector,所有方法都使用了
synchronized关键字修饰。 - 性能较差:由于继承 Vector,存在不必要的同步开销。
Stack 类在 Vector 基础上的扩展方法:
E push(E item):将元素压入栈顶。返回被压入的元素。E pop():删除并返回栈顶元素。栈为空时,抛出EmptyStackException。E peek():获取栈顶元素(不删除)。栈为空时,抛出EmptyStackException。boolean empty():判断栈是否为空。int search(Object o):返回元素在栈中的位置(从栈顶开始计数,栈顶位置为 1)。找不到时返回 -1。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Vector;
import java.util.Stack;
public class Main {
public static void main(String[] args) {
ArrayList <String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
System.out.println("\nArrayList: ");
System.out.println(arrayList);
System.out.println(arrayList.get(1));
arrayList.set(1, "d");
System.out.println(arrayList);
arrayList.remove(0);
System.out.println(arrayList);
LinkedList <String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
System.out.println("LinkedList: ");
System.out.println(linkedList);
System.out.println("\nLinkedList 特有方法: ");
linkedList.addFirst("开头");
linkedList.addLast("结尾");
System.out.println(linkedList);
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
linkedList.removeFirst();
linkedList.removeLast();
System.out.println(linkedList);
Vector <String> vector = new Vector<>();
vector.add("A");
vector.add("B");
vector.add("C");
System.out.println("\nVector: ");
System.out.println(vector);
System.out.println(vector.get(1));
vector.set(1, "D");
System.out.println(vector);
vector.remove(0);
System.out.println(vector);
System.out.println("Vector 特有方法: ");
System.out.println(vector.firstElement());
System.out.println(vector.lastElement());
Stack <String> stack = new Stack<>();
stack.push("a");
stack.push("b");
stack.push("c");
System.out.println("\nStack: ");
System.out.println(stack);
System.out.println(stack.peek());
System.out.println(stack.empty());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
stack.push("x");
stack.push("y");
System.out.println(stack);
System.out.println(stack.search("x"));
System.out.println(stack.search("a"));
System.out.println(stack.search("z"));
}
}
一句话总结:查询多用 ArrayList,增删多用 LinkedList,线程安全用 Vector。
Set 接口
Set 接口继承自 Collection 接口,但它与 List 不同,Set 没有扩展任何与索引相关的方法。因为 Set 代表的是数学意义上的集合,它的特点是无序、不可重复。
简单来说:Set = Collection + 不可重复。
Set 接口的核心特点:
- 元素不可重复:Set 中不能包含两个相同的元素。
- 元素无序:不保证元素的存储顺序和遍历顺序一致。
- 没有索引:不能通过下标访问元素,也没有 get(int index) 方法。
HashSet、LinkedHashSet 和 TreeSet 是 Set 接口的三个主要实现类,它们都实现了 Set 接口的所有方法,但内部实现机制和特点不同。
HashSet 类
HashSet 基于哈希表实现(实际是 HashMap),具有无序、去重、查询快的特点。
核心特点:
- 元素无序:不保证遍历顺序与插入顺序一致。
- 去重原理:通过
hashCode()和equals()方法判断元素是否重复。 - 允许 null 值:只能存储一个 null。
- 时间复杂度:添加、删除、查找都是 O(1)。
HashSet 没有扩展任何特有方法,完全使用 Set 接口的方法。
LinkedHashSet 类
LinkedHashSet 继承自 HashSet,底层是哈希表 + 双向链表,具有有序、去重的特点。
核心特点:
- 元素有序:按照插入顺序排列(比 HashSet 多维护了一个链表)。
- 去重原理:与 HashSet 相同,通过 hashCode() 和 equals()。
- 性能略低于 HashSet:因为需要维护链表。
- 允许 null 值:只能存储一个 null。
LinkedHashSet 没有扩展任何特有方法,完全使用 Set 接口的方法。
TreeSet 类
TreeSet 基于红黑树实现(实际是 TreeMap),具有自动排序、去重的特点。
TreeSet 底层使用 TreeMap,而 TreeMap 的核心就是红黑树。
核心特点:
- 自动排序:元素按照自然顺序排序,或通过 Comparator 自定义排序
- 去重原理:通过
compareTo()或compare()方法的返回值判断是否重复(返回 0 表示重复) - 不允许 null:TreeSet 不允许存储 null 值(会抛出 NullPointerException)
- 时间复杂度:添加、删除、查找都是 O(log n)
TreeSet 类在 Set 接口基础上的扩展方法:
E first():获取第一个(最小)元素。E last():获取最后一个(最大)元素。E ceiling(E e):返回大于等于给定元素的最小元素。E floor(E e):返回小于等于给定元素的最大元素。E higher(E e):返回严格大于给定元素的最小元素。E lower(E e):返回严格小于给定元素的最大元素。SortedSet headSet(E toElement):返回小于指定元素的子集。SortedSet tailSet(E fromElement):返回大于等于指定元素的子集。SortedSet subSet(E fromElement, E toElement):返回指定范围的子集。Comparator comparator():返回比较器(自然排序返回 null)。E pollFirst():获取并删除第一个元素。E pollLast():获取并删除最后一个元素。
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
// ========== 1. HashSet:无序,去重 ==========
HashSet<String> hashSet = new HashSet<>();
hashSet.add("a");
hashSet.add("b");
hashSet.add("c");
hashSet.add("a"); // 重复元素,不会被添加
System.out.println("\nHashSet: ");
System.out.println(hashSet); // 顺序不确定
System.out.println(hashSet.size()); // 3
System.out.println(hashSet.contains("b")); // true
hashSet.remove("b");
System.out.println(hashSet);
hashSet.add(null);
System.out.println(hashSet);
// ========== 2. LinkedHashSet:保持插入顺序 ==========
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("a");
linkedHashSet.add("b");
linkedHashSet.add("c");
linkedHashSet.add("a"); // 重复,不会添加
System.out.println("\nLinkedHashSet: ");
System.out.println(linkedHashSet); // [a, b, c]
System.out.println(linkedHashSet.size()); // 3
System.out.println(linkedHashSet.contains("b")); // true
linkedHashSet.remove("b");
System.out.println(linkedHashSet);
// ========== 3. TreeSet:自动排序 ==========
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("c");
treeSet.add("a");
treeSet.add("b");
treeSet.add("a"); // 重复,不会添加
System.out.println("\nTreeSet: ");
System.out.println(treeSet); // [a, b, c](自动排序)
System.out.println(treeSet.size()); // 3
System.out.println(treeSet.contains("b")); // true
treeSet.remove("b");
System.out.println(treeSet);
// TreeSet 特有方法
System.out.println("\nTreeSet 特有方法: ");
treeSet.add("c");
treeSet.add("d");
treeSet.add("a");
System.out.println(treeSet); // [a, c, d]
System.out.println(treeSet.first()); // a(最小)
System.out.println(treeSet.last()); // d(最大)
System.out.println(treeSet.ceiling("b")); // c(>=b的最小元素)
System.out.println(treeSet.floor("b")); // a(<=b的最大元素)
System.out.println(treeSet.higher("c")); // d(>c的最小元素)
System.out.println(treeSet.lower("c")); // a(<c的最大元素)
System.out.println(treeSet.headSet("c")); // [a](<c)
System.out.println(treeSet.tailSet("c")); // [c, d](>=c)
System.out.println(treeSet.subSet("b", "d")); // [c](b<=x<d)
System.out.println(treeSet.pollFirst()); // a(删除并返回最小)
System.out.println(treeSet.pollLast()); // d(删除并返回最大)
System.out.println(treeSet);
}
}
EnumSet 抽象类
EnumSet 是专门为枚举类型设计的 Set 集合,它是抽象类,不能直接实例化。EnumSet 利用枚举类型的特性,内部用位向量(bit vector)实现,是所有 Set 实现中性能最好、内存最紧凑的。
简单来说:EnumSet = 枚举专用的高性能 Set。
EnumSet 只能存储枚举类型,每个枚举常量对应一个 bit 位,极度节省内存,按照枚举常量声明的顺序遍历,不能添加 null 元素。
时间复杂度:所有操作都是 O(1)。
EnumSet 是抽象类,不能直接 new,但提供了丰富的静态工厂方法:
import java.util.EnumSet;
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public class Main {
public static void main(String[] args) {
EnumSet<Day> allDays = EnumSet.allOf(Day.class);
System.out.println(allDays);
EnumSet<Day> emptyDays = EnumSet.noneOf(Day.class);
System.out.println(emptyDays);
EnumSet<Day> workDays = EnumSet.of(Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, Day.THURSDAY, Day.FRIDAY);
System.out.println(workDays);
EnumSet<Day> midWeek = EnumSet.range(Day.TUESDAY, Day.THURSDAY);
System.out.println(midWeek);
EnumSet<Day> weekend = EnumSet.complementOf(workDays);
System.out.println(weekend);
EnumSet<Day> copyDays = EnumSet.copyOf(workDays);
System.out.println(copyDays);
}
}
一句话总结:去重用 HashSet,要顺序用 LinkedHashSet,要排序用 TreeSet,枚举用 EnumSet。**
Queue 接口
Queue 接口继承自 Collection 接口,它在 Collection 的基础上扩展了队列操作相关的方法。队列是一种先进先出的数据结构。
简单来说:Queue = Collection + 队列操作(先进先出)。
Queue 接口的核心特点:
- 先进先出:元素按照插入的顺序被处理,先插入的先被取出。
- 队头/队尾:队列有两个端点,插入在队尾,取出在队头。
- 不能随机访问:不能通过索引获取元素,只能从队头操作。
Queue 接口在 Collection 接口基础上的扩展方法:
add(E e):在队尾添加元素。失败时,抛出异常。offer(E e):在队尾添加元素。失败时,返回 false。remove():删除并返回队头元素。失败时,抛出异常。poll():删除并返回队头元素。失败时,返回 null。element():获取队头元素(不删除)。失败时,抛出异常。peek():获取队头元素(不删除)。失败时,返回 null。
LinkedList、PriorityQueue 和 ArrayDeque 是 Queue 接口的三个主要实现类,它们都实现了 Queue 接口的所有方法,但内部实现机制和特点不同。
LinkedList 类
LinkedList 基于双向链表实现,既可以作为普通队列使用,也可以作为双端队列使用。
LinkedList 实现了 List 和 Deque 两个接口,意味着它的核心类型是 列表 + 双端队列,允许 null 元素,插入、删除操作效率高 O(1)。
PriorityQueue 类
PriorityQueue 基于堆(数组)实现,元素按照优先级排序,而不是按照插入顺序。
PriorityQueue 元素按照自然顺序或自定义 Comparator 排序,最小的元素在队头(默认升序),不允许存储 null 值,插入 O(log n),取出 O(log n)。
PriorityQueue 只实现了 Queue 接口,没有实现 List。
时间复杂度:插入 O(log n),取出 O(log n)。
PriorityQueue 类在 Queue 接口基础上的扩展方法:
PriorityQueue(int initialCapacity):指定初始容量。PriorityQueue(Comparator comparator):指定比较器。PriorityQueue(int initialCapacity, Comparator comparator):指定容量和比较器。comparator():返回用于排序的比较器。
ArrayDeque 类
ArrayDeque 基于循环数组实现,是比 LinkedList 更高效的 队列/双端队列 实现。
ArrayDeque 实现了 Deque 接口,没有实现 List 接口,因此它的核心类型是 双端队列。
核心特点:
- 循环数组:不需要扩容时移动元素,性能更好。
- 不允许 null:ArrayDeque 不允许存储 null 值。
- 作为队列使用:比 LinkedList 更快。
- 作为栈使用:比 Stack 类更快。
- 时间复杂度:插入、删除都是 O(1)。
import java.util.Queue;
import java.util.Deque;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.ArrayDeque;
public class Main {
public static void main(String[] args) {
Queue <String> linkedList = new LinkedList<>();
linkedList.offer("a");
linkedList.offer("b");
linkedList.offer("c");
System.out.println("\nLinkedList:");
System.out.println(linkedList);
System.out.println(linkedList.peek());
System.out.println(linkedList.poll());
System.out.println(linkedList);
Queue <Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(5);
priorityQueue.offer(1);
priorityQueue.offer(8);
priorityQueue.offer(3);
System.out.println("\nPriorityQueue:");
System.out.println("插入顺序: 5,1,8,3");
System.out.println("取出顺序(从小到大):");
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
Deque <String> arrayDeque = new ArrayDeque<>();
arrayDeque.offer("a");
arrayDeque.offer("b");
arrayDeque.offer("c");
System.out.println("\nArrayDeque:");
System.out.println(arrayDeque);
// 双端操作
arrayDeque.addFirst("开头");
arrayDeque.addLast("结尾");
System.out.println(arrayDeque);
System.out.println(arrayDeque.getFirst());
System.out.println(arrayDeque.getLast());
}
}
一句话总结:普通先进先出队列用 ArrayDeque,需要优先级排序用 PriorityQueue,需要同时作为列表使用用 LinkedList。
Deque 接口
Deque 接口继承自 Queue 接口,它在 Queue 的基础上扩展了双端操作相关的方法。Deque 是 "Double Ended Queue"(双端队列)的缩写,代表一个可以在两端进行插入和删除操作的队列。
简单来说:Deque = Queue + 两端操作。
Deque 接口的核心特点:
- 双端操作:可以在队首和队尾两端添加、删除、获取元素。
- 可以作为队列使用:先进先出(FIFO),从队尾添加,从队首取出。
- 可以作为栈使用:后进先出(LIFO),从队首添加,从队首取出。
- 既可以有容量限制,也可以无容量限制。
Deque 接口在 Queue 基础上扩展的方法:
addFirst(E e):在队首添加元素。失败时,抛出异常。offerFirst(E e):在队首添加元素。失败时,返回 false。addLast(E e):在队尾添加元素。失败时,抛出异常。offerLast(E e):在队尾添加元素。失败时,返回 false。removeFirst():删除并返回队首元素。失败时,抛出异常。pollFirst():删除并返回队首元素。失败时,返回 null。removeLast():删除并返回队尾元素。失败时,抛出异常。pollLast():删除并返回队尾元素。失败时,返回 null。getFirst():获取队首元素(不删除)。失败时,抛出异常。peekFirst():获取队首元素(不删除)。失败时,返回 null。getLast():获取队尾元素(不删除)。失败时,抛出异常。peekLast():获取队尾元素(不删除)。失败时,返回 null。push(E e):在队首添加元素(等价于addFirst(e))。失败时,抛出异常。pop():删除并返回队首元素(等价于removeFirst())。失败时,抛出异常。
Map 接口
Map 接口是 Java 集合框架中的双列集合的根接口,它存储的是键值对。与 Collection 存储单个元素不同,Map 存储的是键(Key)和值(Value)的映射关系,通过键可以快速找到对应的值。
简单来说:Map = 键值对集合(通过键找值)。
Map 接口的核心特点:
- 键值对存储:每个元素包含一个键和一个值。
- 键不可重复:同一个 Map 中,键是唯一的。
- 值可以重复:不同的键可以对应相同的值。
- 通过键访问:不能通过索引访问,只能通过键来获取值。
Map 接口的核心方法:
V put(K key, V value):将指定的键值对放入 Map 中。如果键已存在,则替换旧值并返回旧值;如果键不存在,返回 null。void putAll(Map m):将另一个 Map 中的所有键值对复制到当前 Map 中。V remove(Object key):删除指定键对应的键值对,并返回被删除的值。如果键不存在,返回 null。void clear():删除 Map 中的所有键值对。V get(Object key):返回指定键对应的值。如果键不存在,返回 null。boolean containsKey(Object key):判断 Map 中是否包含指定的键。boolean containsValue(Object value):判断 Map 中是否包含指定的值。int size():返回 Map 中键值对的数量。boolean isEmpty():判断 Map 是否为空。Set keySet():返回所有键组成的 Set 集合(键不可重复)。Collection values():返回所有值组成的 Collection 集合(值可以重复)。Set> entrySet():返回所有键值对组成的 Set 集合。
HashMap、LinkedHashMap 和 TreeMap 是 Map 接口的三个主要实现类,它们都实现了 Map 接口的所有方法,但内部实现机制和特点不同。
Map(接口)
├── AbstractMap(抽象类)
│ ├── HashMap(类)
│ │ └── LinkedHashMap(类)
│ ├── IdentityHashMap(类)
│ ├── WeakHashMap(类)
│ └── EnumMap(类)
├── Hashtable(类)(遗留类,线程安全)
│ └── Properties(类)
├── ConcurrentMap(接口)(并发 Map 接口)
│ └── ConcurrentHashMap(类) new(高并发)
└── SortedMap(接口)(排序 Map 接口)
└── NavigableMap(接口)(导航 Map 接口)
├── TreeMap(类)(红黑树实现)
└── ConcurrentSkipListMap(类)(跳表实现,并发)
遍历 Map 的方式:
import java.util.Map;
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Hello", 85);
map.put("Lizhao", 92);
map.put("李兆", 98);
System.out.println("\n方法一:遍历 keySet(获取键,再取值的):");
for (String key : map.keySet()) {
System.out.println(key + " = " + map.get(key));
}
System.out.println("\n方法二:遍历 entrySet(推荐,效率最高):");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println("\n方法三:遍历 values(只取值,不需要键):");
for (Integer value : map.values()) {
System.out.println(value);
}
System.out.println("\n方法四:forEach + Lambda:");
map.forEach((key, value) -> System.out.println(key + " = " + value));
}
}
SortedMap 接口
SortedMap 接口继承自 Map 接口,在 Map 的基础上扩展了与排序相关的方法。SortedMap 代表一个按键排序的 Map。
简单来说:SortedMap = Map + 键排序。
SortedMap 接口的核心特点:
- 键自动排序:按照键的自然顺序或自定义 Comparator 排序。
- 子 Map 视图:可以获取指定范围的子 Map。
- 首尾操作:可以获取最小和最大的键。
SortedMap 接口在 Map 基础上扩展的核心方法:
Comparator comparator():返回用于排序的比较器。如果使用自然顺序,返回 null。K firstKey():返回当前 Map 中最小的键。K lastKey():返回当前 Map 中最大的键。SortedMap headMap(K toKey):返回键小于 toKey 的子 Map。SortedMap tailMap(K fromKey):返回键大于等于 fromKey 的子 Map。SortedMap subMap(K fromKey, K toKey):返回键在 [fromKey, toKey) 范围内的子 Map。
HashMap 类
HashMap 基于哈希表实现,是 Map 接口最常用的实现类,具有无序、查询快、线程不安全的特点。
HashMap 不保证遍历顺序与插入顺序一致,键不可重复(通过 hashCode() 和 equals() 方法判断键是否重复),允许一个 null 键和多个 null 值。
时间复杂度:添加、删除、查找都是 O(1)。
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
// 不保证排序
HashMap<String, Integer> map = new HashMap<>();
map.put("Hello", 85);
map.put("Lizhao", 92);
map.put("李兆", 98);
System.out.println(map);
System.out.println(map.get("Hello"));
System.out.println(map.get("Lizhao"));
System.out.println(map.containsKey("李兆")); // true
System.out.println(map.containsValue(98)); // true
map.remove("Lizhao");
System.out.println(map);
System.out.println(map.size());
map.put(null, -1);
map.put("null", null);
System.out.println(map.get(null));
System.out.println(map);
}
}
LinkedHashMap 类
LinkedHashMap 继承自 HashMap,底层是哈希表 + 双向链表,具有有序、去重的特点。
LinkedHashMap 元素按照插入顺序排列(默认)或访问顺序,性能略低于 HashMap(需要维护链表),允许一个 null 键和多个 null 值。
LinkedHashMap 构造器参数详解:
new LinkedHashMap<>(16, 0.75f, true)
构造器三个参数分别是:
- 初始容量:哈希表底层数组的初始大小。当元素数量超过
容量 × 负载因子时,哈希表会进行扩容。 - 负载因子:哈希表的填充程度阈值。取值范围是 0 到 1 之间的小数。
- 访问顺序模式:LinkedHashMap 最重要的参数,也是实现 LRU 缓存的关键。布尔值:false-插入顺序(默认)、true-访问顺序。
import java.util.LinkedHashMap;
public class Main {
public static void main(String[] args) {
// 保持插入顺序
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("Hello", 85);
map.put("Lizhao", 92);
map.put("李兆", 98);
System.out.println(map);
System.out.println(map.get("Hello"));
System.out.println(map.get("Lizhao"));
map.put(null, -1);
map.put("null", null);
System.out.println(map.get(null));
System.out.println(map);
// 访问顺序模式(用于实现 LRU 缓存)
LinkedHashMap<String, String> lruMap = new LinkedHashMap<>(16, 0.75f, true);
lruMap.put("a", "1");
lruMap.put("b", "2");
lruMap.put("c", "3");
System.out.println("\n初始顺序: " + lruMap);
// 访问元素(会影响顺序)
lruMap.get("a");
System.out.println("访问后: " + lruMap); // {b=2, c=3, a=1}
}
}
TreeMap 类
TreeMap 基于红黑树实现,具有键自动排序的特点,是 SortedMap 接口的主要实现类
TreeMap 键自动排序(按照键的自然顺序或自定义 Comparator 排序),键不允许 null,值可以为 null。
时间复杂度:添加、删除、查找都是 O(log n)。
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
// // 自然排序(升序)
TreeMap<String, Integer> map = new TreeMap<>();
map.put("Hello", 85);
map.put("Lizhao", 92);
map.put("李兆", 98);
map.put("Zhao", 98);
System.out.println(map);
System.out.println("apple: " + map.get("Lizhao"));
System.out.println("\nTreeMap 特有方法:");
System.out.println(map.firstKey());
System.out.println(map.lastKey());
System.out.println(map.firstEntry());
System.out.println(map.lastEntry());
System.out.println(map.ceilingKey("K"));
System.out.println(map.floorKey("a"));
System.out.println(map.subMap("H", "Z"));
// 自定义排序(降序)
TreeMap<String, Integer> descMap = new TreeMap<>((a, b) -> b.compareTo(a));
descMap.put("Hello", 85);
descMap.put("Lizhao", 92);
descMap.put("李兆", 98);
descMap.put("Zhao", 98);
System.out.println("\n降序排列: " + descMap);
}
}
EnumMap 类
EnumMap 是专门为枚举类型作为键设计的 Map 实现。它继承了 AbstractMap 类,是所有 Map 实现中性能最好、内存最紧凑的实现之一。
简单来说:EnumMap = 键必须是枚举类型的专用 Map。
EnumMap键必须是枚举类型,键自动排序(按照枚举常量声明的顺序遍历),可以用数组下标直接访问,不允许 null 键,允许 null 值。
时间复杂度:所有操作都是 O(1),没有哈希计算开销。
import java.util.EnumMap;
import java.util.Map;
enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
enum Status {
PENDING, PROCESSING, COMPLETED, CANCELLED
}
public class Main {
public static void main(String[] args) {
EnumMap<DayOfWeek, String> schedule = new EnumMap<>(DayOfWeek.class);
schedule.put(DayOfWeek.MONDAY, "星期一");
schedule.put(DayOfWeek.TUESDAY, "星期二");
schedule.put(DayOfWeek.WEDNESDAY, "星期三");
schedule.put(DayOfWeek.FRIDAY, "星期五");
System.out.println(schedule);
System.out.println(schedule.get(DayOfWeek.MONDAY));
System.out.println(schedule.containsKey(DayOfWeek.MONDAY));
System.out.println(schedule.containsKey(DayOfWeek.SATURDAY));
schedule.remove(DayOfWeek.FRIDAY);
System.out.println(schedule);
}
}
Hashtable 类
Hashtable 是 Java 中最早的键值对集合类(从 JDK 1.0 就存在,比 HashMap 更早),它与 HashMap 类似,但所有方法都使用 synchronized 修饰,是线程安全的。
Hashtable 所有方法都用 synchronized 修饰,键和值都不能为 null,不保证遍历顺序,性能较差(同步开销)。
import java.util.Hashtable;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Hashtable<String, Integer> table = new Hashtable<>();
table.put("Hello", 85);
table.put("Lizhao", 92);
table.put("李兆", 98);
table.put("Zhao", 98);
System.out.println(table);
System.out.println(table.get("Lizhao"));
System.out.println(table.containsKey("李兆"));
System.out.println(table.containsValue(98));
table.remove("Lizhao");
System.out.println(table);
}
}
IdentityHashMap 类
IdentityHashMap 基于哈希表实现,但它判断键是否相等时使用的是引用相等(==)(同一个对象),而不是对象相等(equals)(对象内容相同)。
简单来说:IdentityHashMap = HashMap + 引用相等(==)判断。
IdentityHashMap 判断键是否重复时,使用 == 而不是 equals() 方法,允许 null 键和 null 值,不是线程安全的,不保证元素的遍历顺序。
IdentityHashMap 与 HashMap 的核心区别:
| 比较方式 | HashMap | IdentityHashMap |
|---|---|---|
| 判断键是否相等 | equals() 方法 |
==(引用相等) |
| 判断重复的依据 | 对象内容相同 | 必须是同一个对象 |
| hashCode 计算 | 调用对象的 hashCode() |
使用 System.identityHashCode() |
| 适用场景 | 常规存储 | 需要区分不同对象的场景 |
Properties 类
Properties 是 Hashtable 的子类,专门用于处理配置文件。它的键和值都必须是字符串。
Properties 常用方法:
setProperty(String key, String value):设置属性(键和值都是字符串)。getProperty(String key):获取属性,不存在返回 null。getProperty(String key, String defaultValue):获取属性,不存在返回默认值。stringPropertyNames():返回所有键的 Set(字符串类型)。load(InputStream in):从输入流加载属性文件。store(OutputStream out, String comments):保存属性到输出流。list(PrintStream out):列出所有属性到打印流。
import java.util.Properties;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
Properties props = new Properties();
props.put("Hello", "a");
props.put("Lizhao", "b");
props.put("李兆", "c");
props.put("Zhao", "d");
props.setProperty("Hello", "A");
props.setProperty("Lizhao", "B");
props.setProperty("Zhao", "D");
System.out.println(props.getProperty("Lizhao"));
System.out.println(props.getProperty("李兆"));
System.out.println(props.getProperty("Li"));
System.out.println(props.getProperty("Li", "李"));
System.out.println("\n所有属性:");
for (String key : props.stringPropertyNames()) {
System.out.println(key + " = " + props.getProperty(key));
}
System.out.println("\n输出到文件:");
try (FileOutputStream out = new FileOutputStream("config.properties")) {
props.store(out, "应用配置文件");
System.out.println("\n配置文件已保存");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("\n从文件加载:");
Properties loadedProps = new Properties();
try (FileInputStream in = new FileInputStream("config.properties")) {
loadedProps.load(in);
System.out.println("\n加载的配置: " + loadedProps);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("\n系统属性:");
Properties systemProps = System.getProperties();
System.out.println(systemProps.getProperty("java.version"));
System.out.println(systemProps.getProperty("os.name"));
System.out.println(systemProps.getProperty("user.home"));
}
}
一句话总结:HashMap 查询最快,LinkedHashMap 保持插入顺序,TreeMap 键自动排序,EnumMap 键为枚举时专用,Hashtable 线程安全(遗留类,不推荐用),IdentityHashMap 用 == 判断键相等(区分不同对象),Properties 专用于读写配置文件。
Bitset 类
BitSet 是 Java 中一个用于存储位(bit)的向量,它实现了一个可以动态增长的位数组。它是一个由 boolean 值组成的紧凑列表,其中每个位(bit)只能存储 0(false)或 1(true)。
BitSet 的核心特点:
- 基于非负整数索引:每个位都有一个索引位置,从 0 开始。
- 可动态增长:需要时自动扩大容量。
- 支持位运算:可以执行 AND、OR、XOR 等逻辑运算。
- 内存紧凑:每个 boolean 值只占用约 1 位(bit)的空间,极其节省内存。
注意:BitSet 和 Set 接口没有直接的关系,BitSet 没有实现 Set 接口。BitSet 的设计目的是对位序列执行逻辑运算(AND、OR、XOR 等)。
import java.util.BitSet;
public class Main {
public static void main(String[] args) {
BitSet bitSet = new BitSet();
// 设置位(将指定索引的位设为 true)
bitSet.set(0);
bitSet.set(2);
bitSet.set(5);
bitSet.set(100);
System.out.println(bitSet);
System.out.println(bitSet.get(2));
System.out.println(bitSet.get(1));
bitSet.clear(2);
System.out.println(bitSet);
bitSet.flip(0);
System.out.println(bitSet);
System.out.println(bitSet.cardinality());
System.out.println(bitSet.nextSetBit(0));
// 位与运算
BitSet bitSet1 = new BitSet();
bitSet1.set(5);
bitSet1.set(10);
bitSet.and(bitSet1);
System.out.println("/位与运算:" + bitSet);
}
}
一句话总结:BitSet 是存储位的布尔数组,逻辑上只能存 true/false;它在需要极致内存效率的海量标志位场景下(如布隆过滤器、签到系统),仍然是最优选择。
Enumeration 接口
Enumeration 接口是 Java 中最早的迭代器接口,用于遍历集合中的元素。它从 JDK 1.0 就已经存在,是 Iterator 接口的前身,主要用于遍历 Vector、Hashtable 等遗留集合类。
简单来说:Enumeration = 老式迭代器(Iterator 的前身)。
Enumeration 接口的核心特点:
- 历史遗留:从 JDK 1.0 就存在,被 Iterator 取代。
- 只能遍历:只提供遍历功能,不能删除元素。
- 仍在使用:部分遗留类(Vector、Hashtable)还在使用。
Enumeration 接口的核心方法:
boolean hasMoreElements():判断是否还有更多元素。有则返回 true,无则返回 false。E nextElement():返回下一个元素。如果没有更多元素,抛出NoSuchElementException。
import java.util.Enumeration;
import java.util.Vector;
public class Main {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("a");
vector.add("b");
vector.add("c");
Enumeration <String> enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
String element = enumeration.nextElement();
System.out.println(element);
}
}
}
Enumeration 转换为 Iterator:
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
public class Main {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("a");
vector.add("b");
vector.add("c");
// 方法一:使用 Collections.list() 转换为 List
Enumeration<String> enumeration = vector.elements();
List<String> list = Collections.list(enumeration);
System.out.println(list);
// 方法二:使用 Iterator 包装
Enumeration<String> e2 = vector.elements();
Iterator<String> iterator = new Iterator<String>() {
@Override
public boolean hasNext() {
return e2.hasMoreElements();
}
@Override
public String next() {
return e2.nextElement();
}
};
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
}
一句话总结:Enumeration 是 JDK 1.0 的老式迭代器,只能遍历不能删除,已被 Iterator 取代;只有在处理 Vector、Hashtable 等遗留类或老框架 API 时才会用到。
相关问题
JDK 和 JRE 有什么区别?
JDK 是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的 java 应用程序。JDK 是整个 java 开发的核心,它包含了 JAVA 的运行环境和 JAVA 工具。JRE 是一个软件,由太阳微系统所研发,JRE 可以让计算机系统运行 Java 应用程序。
Java 中,哪些类是默认导入的?
在 Java 中,以下包和类型是默认导入的,不需要手动编写 import 语句:
java.lang 包
java.lang 是唯一自动导入的包, 包下的所有类都是默认导入的。
注意:子包不会自动导入,比如:java.lang.reflect 中的类需要手动导入。
核心类:
Object- 所有类的父类Number- 数值类String- 字符串类StringBuilder/StringBuffer- 可变字符串Math- 数学运算类System- 系统类(含System.out)Class- 反射相关Thread- 线程类Runnable- 线程接口Throwable/Exception/Error- 异常体系
包装类:
Byte/Short/Integer/LongFloat/DoubleCharacter/Boolean
其他常用类:
Enum- 枚举类型Iterable/Comparable- 核心接口StackTraceElementRuntime/Process- 运行时相关
与当前类在同一个包下的其他类
同一个包下的其他类,即使没有 import 也可以直接使用。
什么是Lambda 表达式?
Lambda 表达式是 Java 8 引入的一个重要特性,它可以让你把函数作为一个方法的参数传递进去,或者把代码块当作数据来处理。简单来说,Lambda 就是一种简化匿名内部类的写法,让代码更简洁、更易读。
基本语法格式:
- 参数列表:用小括号包裹,类似方法的参数。
- 箭头符号:
->分隔参数和方法体。 - 方法体:用大括号包裹,写具体逻辑。
(参数列表) -> { 方法体 }
// 传统方式:使用匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
};
new Thread(runnable).start();
// Lambda 方式:只需要关注核心逻辑
Runnable runnable = () -> System.out.println("线程执行了");
new Thread(runnable).start();
Lambda 只能用在函数式接口上,即只有一个抽象方法的接口。
// 函数式接口示例
@FunctionalInterface // 注解,表示这是一个函数式接口
interface Calculator {
int calculate(int a, int b); // 只有一个抽象方法
}
// 不是函数式接口(有两个抽象方法)
interface BadInterface {
void method1();
void method2(); // 第二个抽象方法,不能用 Lambda
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// 使用 Lambda 实现函数式接口
Calculator add = (a, b) -> a + b;
Calculator subtract = (a, b) -> a - b;
System.out.println(add.calculate(10, 5));
System.out.println(subtract.calculate(10, 5));
}
}
当 Lambda 体只是调用一个已有的方法时,可以用方法引用简化。
方法引用可以理解为 Lambda 表达式的语法糖,它能直接引用已有方法,而不需要重写。
List<String> names = Arrays.asList("Hello", "Lizhao", "李兆");
// Lambda 方式
names.forEach(name -> System.out.println(name));
// 方法引用方式(更简洁)
names.forEach(System.out::println);
什么是红黑树?
红黑树是一种自平衡的二叉查找树,是计算机科学中一种非常重要的数据结构。它可以在插入、删除、查找操作中保持较好的性能。
红黑树是一种特殊的二叉查找树,它在每个节点上增加了一个颜色属性(红色或黑色),并通过一些规则保证树的大致平衡,从而确保操作的时间复杂度为 O(log n)。
红黑树首先是一棵二叉查找树(BST,Binary Search Tree),所以它必须满足:左子树所有节点的值 < 根节点的值 < 右子树所有节点的值。
红黑树只是在二叉查找树的基础上增加了颜色规则和平衡机制,但核心的大小关系规则保持不变。
五个核心规则:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 所有叶子节点(NIL)是黑色。
- 红色节点的子节点必须是黑色(不能有两个连续的红色节点)。
- 从任意节点到其每个叶子节点的所有路径上,黑色节点数量相同。
50(B)
/ \
30(R) 80(R)
/ \ / \
20(B) 40(B) 70(B) 90(B)
/ \ / \ / \ / \
NIL NIL NIL NIL NIL NIL NIL NIL
说明:
- NIL(叶子节点)都是黑色
- 从根到任意叶子节点的黑色节点数量相同
路径1: 50(B) → 30(R) → 20(B) → NIL = 2个黑色
路径2: 50(B) → 30(R) → 40(B) → NIL = 2个黑色
路径3: 50(B) → 80(R) → 70(B) → NIL = 2个黑色
路径4: 50(B) → 80(R) → 90(B) → NIL = 2个黑色
接口、抽象类、类有什么区别?
- 接口:纯行为规范,定义了一组方法签名,让实现类去完成具体逻辑。不能直接创建对象(new),没有构造器。类可以实现多个接口(多实现),接口可以继承多个接口。
抽象类:不能被实例化的类,它通常作为其他类的父类,提供公共的代码和定义抽象行为。不能直接创建对象(new),可以有构造器(供子类调用),可以有抽象方法,也可以有具体方法,可以有实例变量,子类必须实现所有抽象方法(除非子类也是抽象类)。
类:描述了对象的属性和行为。可以直接创建对象(new),可以有构造器,可以有实例变量,所有方法必须有实现体(不能有抽象方法)。