Java开发教程
一、简介
📢 公司:由 SUN 公司 詹姆斯-高斯林 开发,后被 甲骨文(Oracle)收购。
知名作品:我的世界、淘宝网、Android 操作系统。Java 容器:很多繁琐又重复的工作我们可以提前做好,然后调用,但谁来做呢,有个组织出来定义了接口,谁家都可以造轮子,用户想用哪家的都可以,各家自己造的轮子(如Tomcat、GlassFish、IBM WebSphere)就叫做 Java 容器。随着越来越多的企业加入到这个阵营,官方给出的规范组件并不是最受欢迎的,反而一些企业的组件在实际开发中更让人喜欢。
Java SE,EE,ME 三者的区别
1、开发桌面应用的 Java SE(Java Platform,Standard Edition)
2、开发 Web 应用的 Java EE(Java Platform,Enterprise Edition)
3、Android 开发移动应用 和 嵌入式 的 Java ME(Java Platform,Micro Edition)
4、参考连接: Java SE EE ME具体区别
JAR: Java 类库的 class 文件。
JDK,JRE,JVM 三者的区别:
总的来说,JDK 中包含 JRE,因为开发总要运行嘛,而 JRE 又包含 JVM。具体可以打开我们下载的JDK文件夹,里面又包含了一个JRE文件夹。参考CSDN
1、JDK:JDK(Java Development Kit)称为 Java 开发包 或 Java 开发工具,是一个编写 Java 的 Applet 小程序和应用程序的程序开发环境。JDK 是整个 Java 的核心,包括了 Java 运行环境 JRE(Java Runtime Envirnment),JVM 和 Java 的核心类库(Java API)。 JDK 不同版本特性
2、JRE: JRE(Java Runtime Envirnment)运行java程序的环境,JRE里面只有client运行环境,安装过程中,会自动添加PATH。
3、JVM:Java 虚拟机(Java Virtual Machine)
Java和C++的区别:
- Java是解释型语言,所谓的解释型语言,就是源码会先经过一次编译,成为中间码,中间码再被解释器解释成机器码。对于Java而言,中间码就是字节码(.class),而解释器在JVM中内置了。
- C++是编译型语言,所谓编译型语言,就是源码一次编译,直接在编译的过程中链接了,形成了机器码。
- C++比Java执行速度快,但是Java可以利用JVM跨平台。
- Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程的东西,比如是全局变量和全局函数。
- C++中有指针,Java中没有,但是有引用。
- C++支持多继承,Java中类都是单继承的。但是继承都有传递性,同时Java中的接口是多继承,类对接口的实现也是多实现。
- C++中,开发需要自己去管理内存,但是Java中JVM有自己的GC机制,虽然有自己的GC机制,但是也会出现OOM和内存泄漏的问题。C++中有析构函数,Java中Object的finalize方法
- C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。
Linux、Windows与MAC OS下的换行符
我使用的是 Windows,提交上来的文档总是不能够换行,导致格式很糟糕,于是就去查了一下,是不是提交上来的换行符有问题。参考连接
历史:为了保证打字机换行时消耗的时间内不会有其它字符进来,主动添加了两个无效字符(回车 换行)。
计算机出现后,开始讨论加一个还是两个的问题,注意了 Typora 的软换行在 GitHub 中是无效的,在 Typora 中按下 Shift + Enter 是软换行,按下此组合键后,可以看到换了一行,但是推送到 GitHub 上后,你会发现换行是无效的。要解决这问题,你就要搞清楚空格、软换行、硬换行、换段的在 Typora 中的概念。Typora 在空格与换行部分主要是使用 CommonMark 作为标注规范。与前文提到的 GFM 一样,CommonMark 也是比较流行的 Markdown 语言规范(解析器)之一。
- 空格:在输入连续的空格后,Typora 会在编辑器视图里为你保留这些空格,但当你打印或导出时,这些空格会被省略成一个。
你可以在源代码模式下,为每个空格前加一个\
转义符,或者直接使用 HTML 风格的&nbps;
来保持连续的空格。- 软换行: 需要说明的是,在 Markdown 语法中,换行(line break)与换段是不同的。且换行分为软换行和硬换行。在 Typora 中,你可以通过
Shift + Enter
完成一次软换行。软换行只在编辑界面可见,当文档被导出时换行会被省略。- 硬换行: 你可以通过
空格 + 空格 + Shift + Enter
完成一次硬换行,而这也是许多 Markdown 编辑器所原生支持的。硬换行在文档被导出时将被保留,且没有换段的段后距。- 换段: 你可以通过
Enter
完成一次换段。Typora 会自动帮你完成两次Shift + Enter
的软换行,从而完成一次换段。这也意味着在 Markdown 语法下,换段是通过在段与段之间加入空行来实现的。- Windows 风格(CR+LF)与 Unix 风格(CR)的换行符: CR 表示回车
\r
,即回到一行的开头,而 LF 表示换行\n
,即另起一行。
所以 Windows 风格的换行符本质是「回车 + 换行」,而 Unix 风格的换行符是「换行」。这也是为什么 Unix / Mac 系统下的文件,如果在 Windows 系统直接打开会全部在同一行内。 你可以在文件 - 偏好设置 - 编辑器 - 默认换行符
中对此进行切换。
二、从0-1配置Java
1. 安装 Java 开发环境
1.1 安装 JDK(Java Development Kit)
JDK 是 Java 开发的核心工具包,包含编译器(javac)和 JRE(Java 运行环境)。
- 下载:访问 Oracle JDK 或 OpenJDK,根据系统选择版本(如 Windows 64 位)。
- 安装:运行安装程序,按默认路径安装(如
C:\Program Files\Java\jdk-17
)。 - 配置环境变量:
JAVA_HOME
:指向 JDK 安装路径(如C:\Program Files\Java\jdk-17
)。PATH
:添加%JAVA_HOME%\bin
到系统路径。- 验证:打开命令行输入
java -version
和javac -version
,显示版本号即成功。
1.2 安装集成开发环境(IDE)
推荐使用 IntelliJ IDEA Community Edition(免费)或 Eclipse:
- IntelliJ IDEA:下载地址,安装后启动即可。
常用快捷键:
快捷键 | 作用 |
---|---|
main /psvm 、sout 、… |
快速键入相关代码 |
Ctrl + D |
复制当前行数据到下一行 |
Ctrl + Y |
删除所在行,建议用Ctrl + X |
Ctrl + ALT + L |
格式化代码 |
ALT + SHIFT + ⬆ ,ALT + SHIFT + ⬇ |
上下移动当前行代码 |
Ctrl + / ,Ctrl + SHIFT +/ |
对代码进行注释 |
2. 编写第一个 Java 程序
2.1 创建项目
以 IntelliJ IDEA 为例:
- 打开 IDE,选择
File
→New
→Project
。 - 选择
Java
,SDK 选择已安装的 JDK,点击Create
。
2.2 编写 Hello World
在src
目录下创建Main.java
文件,输入以下代码:
java
public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } }
- 代码解释:
public class Main
:定义一个名为Main
的公共类。public static void main(String[] args)
:程序入口方法,Java 虚拟机(JVM)从这里开始执行。System.out.println()
:向控制台输出内容。
2.3 运行程序
-
在代码编辑器右键点击 →
Run 'Main.main()'
。 -
或使用命令行:
bash
`# 编译Java文件
javac Main.java运行编译后的类
java Main`
-
输出:
Hello, World!
三、Java基础语法
1. 命名与基本方法
- 类名:UpperCamelCase,首字母全部大写
- 方法名:lowerCamelCase,第一个首字母小写
- 源文件名:Java 文件名必须匹配类名。请使用类名保存文件并添加扩展名 “.java”。(如果文件名和类名不相同则会导致编译错误)。
- 关键字与保留字:Java 保留字是指现在 Java 版本尚未使用,但以后版本可能会作为关键字使用。所以注意 false、true、null 等都是保留字。
- Java 标识符:字母、数字、下划线、美元符($)。数字不能作为首位。
- 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。
2. 输入输出方法
使用标准输入输出流。
2.1 标准输入流(System.in
)
- 类型:
InputStream
(字节流) - 作用:从控制台(键盘)读取输入数据
- 通常需要包装为字符流使用,更方便处理文本
示例:使用**System.in
**读取输入
import java.io.IOException;
import java.io.InputStream;
public class StdinExample {
public static void main(String[] args) {
System.out.print("请输入内容: ");
try (InputStream in = System.in) {
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer); // 读取字节
String input = new String(buffer, 0, bytesRead).trim();
System.out.println("你输入了: " + input);
} catch (IOException e) {
e.printStackTrace();
}
}
}
实际开发中,更常用Scanner
或BufferedReader
包装System.in
:
BufferedReader
:主要用于高效读取字符流,特别适合读取大文件或需要缓冲的场景,只能读取字符串类型数据,使用readLine()
读取整行.Scanner
:专门设计用于解析和读取各种类型的数据,包括基本数据类型,可以直接读取多种数据类型(int, double, String 等), 提供多种读取方法如nextLine()
,nextInt()
,nextDouble()
等.
// 使用BufferedReader包装(推荐)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
System.out.print(line);
// 使用Scanner(简单场景)
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.print(input);
2.2 标准输出流(System.out
)
- 类型:
PrintStream
(字节流的子类,提供方便的打印方法) - 作用:向控制台输出数据
- 常用方法:
print()
,println()
,printf()
方法 | 描述 |
---|---|
print() |
将文本或值打印到控制台。 |
printf() |
将格式化文本或值打印到控制台。 |
println() |
将文本或值打印到控制台,并在其后添加新行。 |
示例:使用**System.out
**输出
public class StdoutExample {
public static void main(String[] args) {
System.out.print("这是print,"); // 不自动换行
System.out.println("这是println,会自动换行");
// 格式化输出
String name = "Java";
int version = 17;
System.out.printf("编程语言: %s, 版本: %d%n", name, version);
}
}
2.3 标准错误流(System.err
)
- 类型:
PrintStream
- 作用:向控制台输出错误信息
- 特点:通常显示为红色(取决于终端),用于区分正常输出和错误信息
public class StderrExample {
public static void main(String[] args) {
try {
int result = 10 / 0;
System.out.println("结果: " + result);
} catch (ArithmeticException e) {
// 使用err输出错误信息
System.err.println("发生错误: " + e.getMessage());
}
}
}
2.4 重定向标准流
可以通过System
类的方法重定向标准流,将输入输出指向文件或其他流:
import java.io.FileOutputStream;
import java.io.PrintStream;
public class RedirectStreams {
public static void main(String[] args) throws Exception {
// 保存原始输出流
PrintStream originalOut = System.out;
// 重定向标准输出到文件
System.setOut(new PrintStream(new FileOutputStream("output.txt")));
System.out.println("这句话会被写入文件");
// 恢复原始输出流
System.setOut(originalOut);
System.out.println("这句话会显示在控制台");
}
}
3. 注释
//
:单行注释/* */
:多行注释
4. 变量
String
- 存储文本,例如 “Hello”。字符串值用双引号括起来int
- 存储整数(whole numbers),没有小数,例如 123 或 -123float
- 存储带小数的浮点数,例如 19.99 或 -19.99char
- 存储单个字符,例如 ‘a’ 或 ‘B’。 char 值用单引号括起来boolean
- 以两种状态存储值:true 或 false
声明(创建)变量
如需创建变量,必须指定类型并为其赋值:
typevariable =value;
其中 type 是 Java 的一种类型(比如 int
或者 String
),variableName 是变量的名称(比如 x 或者 name)。等号用于为变量赋值。
Final变量
final
关键字(这会将变量声明为 “final” 或 “constant”,表示不可更改和只读):
final int myNum = 15;
myNum = 20; // 将产生错误:无法为 final 变量赋值
5. 数据类型
数据类型分为两组:
- 原始数据类型 - 包括
byte
、short
、int
、long
、float
、double
、boolean
和char
- 非原始数据类型 - 例如 字符串、数组 和 类
5.1 原始数据类型
原始数据类型指定变量值的大小和类型,并且不拥有额外的方法。
Java 中有八种原始数据类型:
数据类型 | 大小 | 描述 |
---|---|---|
byte |
1 字节 | 存储从 -128 到 127 的整数。 |
short |
2 字节 | 存储从 -32,768 到 32,767 的整数。 |
int |
4 字节 | 存储从 -2,147,483,648 到 2,147,483,647 的整数。 |
long |
8 字节 | 存储从 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 的整数。 |
float |
4 字节 | 存储小数。足以存储 6 到 7 位十进制数字。 |
double |
8 字节 | 存储小数。足以存储 15 位十进制数字。 |
boolean |
1 比特 | 存储 true 值或 false 值。 |
char |
2 字节 | 存储单个字符/字母或 ASCII 值。 |
5.2 数字
原始数字类型分为两组:
- 整数类型 存储整数,正数或负数(例如 123 或 -456),没有小数。有效类型为
byte
、short
、int
和long
。您应该使用哪种类型取决于数值。 - 浮点类型 表示带有小数部分的数字,包含一位或多位小数。有两种类型:
float
和double
。float
的精度只有六位或七位小数,而double
变量的精度约为 15 位。因此,对于大多数计算来说,使用double
更安全。
**! 提示:**尽管 Java 中有许多数字类型,但最常用于数字的是 int
(整数)和 double
(浮点数)。
// 示例
byte byte_Num = 100;
short short_Num = 5000;
int int_Num = 100000;
long long_Num = 15000000000L;
float float_Num = 5.75f;
double double_Num = 19.99d;
boolean isJavaFun = true;
char myGrade = 'B';
String greeting = "Hello World";
5.3 非原始数据类型
非原始数据类型称为_引用类型_,因为它们引用对象。
_原始数据类型和非原始_数据类型之间的主要区别是:
- Java 中预定义(已经定义了)原始类型。非原始类型是由程序员创建的,不是由 Java 定义的(
String
除外)。 - 非原始类型可用于调用方法来执行某些操作,而原始类型则不能。
- 原始类型总是有值,而非原始类型可以为
null
。 - 原始类型以小写字母开头,而非原始类型以大写字母开头。
6. 类型转换
类型转换指的是将一种原始数据类型的值赋给另一种类型。
在 Java 中,有两种类型的转换:
- 放大转换(Widening Casting)(自动) - 将较小的类型转换为较大的类型
byte
->short
->char
->int
->long
->float
->double
- 缩小转换(Narrowing Casting)(手动) - 将较大的类型转换为较小的类型
double
->float
->long
->int
->char
->short
->byte
// 放大转换自动完成
int myInt = 9;
double myDouble = myInt; // 自动转换:int 到 double
// 缩小转换必须再值前面的括号中指明转换类型
double myDouble = 9.78d;
int myInt = (int) myDouble; // 手动转换:double 转换为 int
7. 运算符
7.1 算数运算符
运算符 | 名称 | 描述 | 例子 |
---|---|---|---|
+ | 加 | 将两个值相加 | x + y |
- | 减 | 从另一个值中减去一个值 | x - y |
* | 乘 | 两个值相乘 | x * y |
/ | 除 | 将一个值除以另一个值 | x / y |
% | 取余 | 返回除法余数 | x % y |
++ | 递增 | 将变量的值加 1 | ++x |
– | 递减 | 将变量的值减 1 | --x |
7.2 赋值运算符
运算符 | 例子 | 等同于 |
---|---|---|
= | x = 5 |
x = 5 |
+= | x += 3 |
x = x + 3 |
-= | x -= 3 |
x = x - 3 |
*= | x *= 3 |
x = x * 3 |
/= | x /= 3 |
x = x / 3 |
%= | x %= 3 |
x = x % 3 |
&= | x &= 3 |
x = x & 3 |
= | `x | |
^= | x ^= 3 |
x = x ^ 3 |
>>= | x >>= 3 |
x = x >> 3 |
<<= | x <<= 3 |
x = x << 3 |
7.3 比较运算符
运算符 | 名称 | 等同于 |
---|---|---|
== | 等于 | x == y |
!= | 不相等 | x != y |
> | 大于 | x > y |
< | 小于 | x < y |
>= | 大于或等于 | x >= y |
<= | 小于或等于 | x <= y |
7.4 逻辑运算符
运算符 | 名称 | 描述 | 例子 |
---|---|---|---|
&& | 逻辑和 | 如果两条语句都为真,则返回 true。 | x < 5 && x < 10 |
逻辑或 | |||
! | 逻辑否 | 反转结果。如果结果为真,则返回 false。 | !(x < 5 && x < 10) |
8. 字符串
String
是不可变对象,所有修改操作(如replace
、substring
)都会返回新的字符串,原字符串不变。- 频繁拼接字符串时,建议使用
StringBuilder
或StringBuffer
以提高性能。
8.1 常用字符串方法
length()
:返回字符串长度
String str = "Hello";
int len = str.length(); // 结果:5
isEmpty()
:判断字符串是否为空(长度为 0)
"".isEmpty(); // true
"a".isEmpty(); // false
isBlank()
(Java 11+):判断字符串是否为空或仅包含空白字符
" ".isBlank(); // true
" a ".isBlank(); // false
toUpperCase()
:将字符串全部转换为大写字母
String txt = "Hello World";
System.out.println(txt.toUpperCase()); // 输出 "HELLO WORLD"
toLowerCase()
:将字符串全部转换为小写字母
String txt = "Hello World";
System.out.println(txt.toLowerCase()); // 输出 "hello world"
trim()
:去除首尾空白字符(不包括中间)
" Hello ".trim(); // "Hello"
strip()
(Java 11+):去除首尾空白字符(支持 Unicode 空白)
" Hello ".strip(); // "Hello"(全角空格也会被去除)
replace(char oldChar, char newChar)
:替换所有指定字符
"Hello".replace('l', 'x'); // "Hexxo"
replace(CharSequence target, CharSequence replacement)
:替换所有指定子串
"Hello World".replace("World", "Java"); // "Hello Java"
replaceAll(String regex, String replacement)
:按正则表达式替换
"a1b2c3".replaceAll("\\d", ""); // "abc"(移除所有数字)
toCharArray()
:转换为字符数组
char[] arr = "abc".toCharArray(); // ['a','b','c']
valueOf(...)
:静态方法,将其他类型转换为字符串
String.valueOf(123); // "123"
String.valueOf(true); // "true"
join(CharSequence delimiter, CharSequence... elements)
:拼接多个字符串并添加分隔符
String.join("-", "2023", "10", "01"); // "2023-10-01"
8.2 字符串的截取与拆分
substring(int beginIndex)
:从指定索引截取到末尾
"Hello".substring(2); // "llo"
substring(int beginIndex, int endIndex)
:截取 [begin, end) 区间的子串
"Hello".substring(1, 4); // "ell"
split(String regex)
:按正则表达式拆分字符串为数组
"a,b,c".split(","); // ["a", "b", "c"]
8.3 字符串的查找与判断
contains(CharSequence s)
:判断是否包含指定字符序列
"Hello World".contains("World"); // true
startsWith(String prefix)
:判断是否以指定前缀开头
"Hello".startsWith("He"); // true
endsWith(String suffix)
:判断是否以指定后缀结尾
"file.txt".endsWith(".txt"); // true
indexOf(String str)
:返回指定子串首次出现的索引(未找到返回 - 1)
"abcabc".indexOf("ab"); // 0
lastIndexOf(String str)
:返回指定子串最后出现的索引
"abcabc".lastIndexOf("ab"); // 3
charAt(int index)
:返回指定索引的字符
"Hello".charAt(1); // 'e'
8.4 字符串的拼接与比较
concat(String str)
:拼接字符串(等价于+
运算符)
"Hello".concat(" World"); // "Hello World"
// 直接使用+也可以
String str = "Hello" + "World";
equals(Object anObject)
:判断内容是否相等(区分大小写)
"Hello".equals("hello"); // false
equalsIgnoreCase(String anotherString)
:忽略大小写比较内容
"Hello".equalsIgnoreCase("hello"); // true
compareTo(String anotherString)
:按字典顺序比较(返回整数)
"a".compareTo("b"); // -1(a在b前)
"b".compareTo("a"); // 1(b在a后)
"a".compareTo("a"); // 0(相等)
8.5 字符串中的转义字符
转义字符 | 结果 | 描述 |
---|---|---|
' | ’ | 单引号 |
" | " | 双引号 |
\ | \ | 反斜杠 |
9. 数学方法
注意:所有数学方法都是 static
(静态的)。
方法 | 描述 | 返回类型 |
---|---|---|
abs(x) | 返回 x 的绝对值。 | double |
acos(x) | 返回 x 的反余弦值,以弧度为单位。 | double |
addExact(x, y) | 返回 x 和 y 的和。 | int |
asin(x) | 返回 x 的反正弦,以弧度为单位。 | double |
atan(x) | 返回 x 的反正切值,以 -PI/2 和 PI/2 弧度之间的数值。 | double |
atan2(y,x) | 返回从直角坐标 (x, y) 转换为极坐标 (r, theta) 的角度 theta。 | double |
cbrt(x) | 返回 x 的立方根。 | double |
ceil(x) | 返回 x 的值向上舍入到最接近的整数。 | double |
copySign(x, y) | 返回第二个浮点 y 的符号和第一个浮点 x。 | double |
cos(x) | 返回 x 的余弦值(x 以弧度为单位)。 | double |
cosh(x) | 返回 double 值的双曲余弦值。 | double |
decrementExact(x) | 返回 x-1。 | int |
exp(x) | 返回 Ex 的值。 | double |
expm1(x) | 返回 ex -1。 | double |
floor(x) | 返回向下舍入到最接近的整数的 x 的值。 | double |
floorDiv(x, y) | 返回 x 除以 y 向下取整的结果。 | int |
floorMod(x, y) | 返回 x 除以 y 的余数,其中除法结果已向下取整。 | int |
getExponent(x) | 返回 x 中使用的无偏指数(unbiased exponent)。 | int |
hypot(x, y) | 返回没有中间溢出或下溢的 sqrt(_x_2 + _y_2) 。 | double |
IEEEremainder(x, y) | 规定计算 x 和 y 的余数运算,按照 IEEE 754 标准。 | double |
incrementExact(x) | 返回 x+1 | int |
log(x) | 返回 x 的自然对数(以 E 为底)。 | double |
log10(x) | 返回 x 的以 10 为底的对数。 | double |
log1p(x) | 返回 x 和 1 之和的自然对数(以 E 为底)。 | double |
max(x, y) | 返回有最高值的数字。 | double |
min(x, y) | 返回有最小值的数字。 | double |
multiplyExact(x, y) | 返回 x 与 y 的乘积结果。 | int |
negateExact(x) | 返回 x 的相反数。 | int |
nextAfter(x, y) | 返回 y 方向与 x 相邻的浮点数。 | double |
nextDown(x) | 返回在负方向上与 x 相邻的浮点值。 | double |
nextUp(x) | 返回正无穷大方向上与 x 相邻的浮点值。 | double |
pow(x, y) | 返回 x 的 y 次方的值。 | double |
random() | 返回 0 到 1 之间的随机数。 | double |
rint(x) | 返回最接近 x 且等于某个数学整数的双精度值。 | double |
round(x) | 返回舍入到最接近整数的 x 的值。 | int |
scalb(x, y) | 返回 x 乘以 2 的 y 次幂的结果。 | double |
signum(x) | 返回 x 的符号。 | double |
sin(x) | 返回 x 的正弦值(x 以弧度为单位)。 | double |
sinh(x) | 返回双精度值的双曲正弦值。 | double |
sqrt(x) | 返回 x 的平方根。 | double |
subtractExact(x, y) | 返回 x 减去 y 的结果。 | int |
tan(x) | 返回角度的正切。 | double |
tanh(x) | 返回 double 值的双曲正切值。 | double |
toDegrees(x) | 将以弧度为单位的角度转换为近似值。以度为单位的等效角度。 | double |
toIntExact(x) | 将长整型值转换为整型。 | int |
toRadians(x) | 将以度为单位的角度转换为近似值。以弧度为单位的角度。 | double |
ulp(x) | 返回 x 的最小精度单位 (ulp) 的大小。 | double |
10. if…else…
判断
eg:
int myNum = 10; // 这是一个正数还是负数?
if (myNum > 0) {
System.out.println("该值为正数。");
} else if (myNum < 0) {
System.out.println("该值为负数。");
} else {
System.out.println("该值为 0。");
}
note: 可以用三元运算符代替if…else…
int time = 20;
if (time < 18) {
System.out.println("早安");
} else {
System.out.println("晚安");
}
// 可以写为
int time = 20;
String result = (time < 18) ? "早安" : "晚安";
System.out.println(result);
11. Switch
判断
eg:
int day = 4;
switch (day) {
case 6:
System.out.println("Today is Saturday");
break;
case 7:
System.out.println("Today is Sunday");
break;
default:
System.out.println("Today is WorkDay");
}
// 输出 "Today is WorkDay"
12. While
循环
12.1 while
eg:
int i = 0;
while (i < 5) {
System.out.println(i);
i++;
}
12.2 do…while…
eg:
int i = 0;
do {
System.out.println(i);
i++;
}
while (i < 5);
13. For
循环
eg:
for (int i = 0; i <= 10; i = i + 2) {
System.out.println(i);
}
还有一个 “for-each” 循环,专门用于遍历_数组_中的元素:
String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
for (String i : cars) {
System.out.println(i);
}
Note: break
语句可用于跳出_循环_。continue
语句会中断一次迭代(在循环中),并继续循环中的下一次迭代。
14. 数组
14.1 声明数组
声明数组,请使用_方括号_定义变量类型:
String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
int[] myNum = {10, 20, 30, 40};
14.2 数组的访问与修改
- 访问元素:通过索引(从 0 开始)访问
int first = nums[0]; // 获取第一个元素
String second = names[1]; // 获取第二个元
- 修改元素:通过索引重新赋值
nums[2] = 100; // 将第三个元素改为100
names[0] = "Anna"; // 修改第一个元素
// 注意:访问超出数组长度的索引会抛出ArrayIndexOutOfBoundsException
14.3 数组排序
- 使用
java.util.Arrays
工具类的sort()
方法:
import java.util.Arrays;
int[] arr = {3, 1, 4, 2};
Arrays.sort(arr); // 升序排序,结果:[1, 2, 3, 4]
// 对字符串数组排序(按字典顺序)
String[] strs = {"banana", "apple", "cherry"};
Arrays.sort(strs); // 结果:["apple", "banana", "cherry"]
- 自定义排序(对象数组):
// 定义学生类
class Student {
String name;
int age;
// 添加构造函数
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 可选:添加toString方法便于输出
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
// 对学生数组按年龄排序(在main方法中使用)
Student[] students = {new Student("Bob", 20), new Student("Alice", 18)};
Arrays.sort(students, (s1, s2) -> s1.age - s2.age);
14.4 数组的查找
- 线性查找:遍历数组逐个比较
public static int findIndex(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i; // 找到返回索引
}
}
return -1; // 未找到返回-1
}
- 二分查找:需先排序,效率更高(使用
Arrays.binarySearch()
)
int[] sortedArr = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(sortedArr, 3); // 返回2(找到)
int notFound = Arrays.binarySearch(sortedArr, 6); // 返回负数(未找到
14.5 数组的复制
Arrays.copyOf()
:复制指定长度的数组
int[] original = {1, 2, 3};
int[] copy = Arrays.copyOf(original, 5); // 复制并扩容到5个元素,结果:[1,2,3,0,0]
System.arraycopy()
:指定源数组、起始位置、目标数组、目标起始位置和复制长度
int[] src = {10, 20, 30};
int[] dest = new int[5];
System.arraycopy(src, 1, dest, 2, 2); // 从src[1]复制2个元素到dest[2],结果:[0,0,20,30,0]
14.6 数组的转换
- 数组转字符串:使用
Arrays.toString()
int[] arr = {1, 2, 3};
String str = Arrays.toString(arr); // 结果:"[1, 2, 3]"
- 数组转集合:使用
Arrays.asList()
(注意:返回的是固定大小的集合)
String[] strs = {"a", "b", "c"};
List<String> list = Arrays.asList(strs);
14.7 多维数组
// 初始化二维数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 遍历二维数组
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
四、Java方法
1. Java 方法
_方法_是仅在被调用时运行的代码块。
将数据(称为参数)传递到方法中。
方法用于执行特定的操作,也被称为_函数_。
为什么要使用方法?重用代码:定义一次代码,多次使用。
📢 这玩意其实就是函数,Java中称之为方法,非要叫的这么高大上。
方法必须在类中声明。它用方法名定义,后跟括号 ()
。 Java 提供了一些预定义的方法,例如 System.out.println()
,但也可以创建自己的方法来执行某些操作:
如需在 Java 中调用方法,请写方法名称,后跟两个括号 ()
和一个分号;
在下例中,myMethod()
用于在调用时打印文本(操作):
public class Main {
static void myMethod() {
System.out.println("Hello world!");
}
public static void main(String[] args) {
myMethod();
}
}
// 输出 "Hello world!"
2. 方法重载(函数重载)
可以使得多个方法可以拥有相同的名称和不同的参数:
**注意:**只要参数的数量和/或类型不同,多个方法就可以拥有相同的名称。
static int plusMethod(int x, int y) {
return x + y;
}
static double plusMethod(double x, double y) {
return x + y;
}
public static void main(String[] args) {
int myNum1 = plusMethod(8, 5);
double myNum2 = plusMethod(4.3, 6.26);
System.out.println("int: " + myNum1);
System.out.println("double: " + myNum2);
}
3. 作用域
在 Java 中,只能在变量被创建的区域内对其访问。这称为_作用域_。
3.1 方法作用域
直接在方法内部声明的变量,可在方法中声明它们的代码行之后的任何位置使用:
public class Main {
public static void main(String[] args) {
// 此处的代码不能使用 x
int x = 100;
// 此处的代码能够使用 x
System.out.println(x);
}
}
3.2 块作用域
代码块是指大括号 {}
之间的所有代码。在代码块内声明的变量只能由大括号之间的代码访问,且这些代码位于声明变量的行之后:
public class Main {
public static void main(String[] args) {
// 此处的代码不能使用 x
{ // 这是代码块
// 此处的代码不能使用 x
int x = 100;
// 此处的代码能够使用 x
System.out.println(x);
} // 代码块在此处结束
// 此处的代码不能使用 x
}
}
4. Java 递归
递归(Recursion)是使函数调用其本身。
**eg:**使用递归将 5 到 10 之间的所有数字相加
public class Main {
public static void main(String[] args) {
int result = sum(5, 10);
System.out.println(result);
}
public static int sum(int start, int end) {
if (end > start) {
return end + sum(start, end - 1);
} else {
return end;
}
}
}
Note:递归必须有停止条件,避免递归陷入死循环。
五、Java 类
1. Java 类和对象
- 类是对现实世界中事物的抽象(比如 “人” 可以抽象为
Person
类,包含 “姓名、年龄” 等属性和 “吃饭、睡觉” 等方法)。 - 所有的 Java 代码(变量、方法、逻辑)都必须定义在类中,没有 “类外的代码”。
- 程序的运行也是以类为单位:通过类创建对象(
new 类名()
),通过对象调用方法来完成功能。 - 类名使用大驼峰命名法(每个单词首字母大写)
1.1 类的定义语法与示例
[修饰符] class 类名 [extends 父类名] [implements 接口名列表] {
// 成员变量(属性)
// 成员方法(行为)
// 构造方法
// 内部类
}
示例:
// 定义一个Person类
public class Person {
// 成员变量(属性)
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 成员方法(行为)
public void sayHello() {
System.out.println("Hello, my name is " + name);
}
// getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1.2 类定义中的关键字
**访问控制修饰符**
用于控制类、成员变量、方法的访问权限。
修饰符 | 类内部 | 同一包 | 子类 | 其他包 | 可修饰对象 | 说明 |
---|---|---|---|---|---|---|
public | √ | √ | √ | √ | 类、成员变量、方法 | 所有类均可访问该代码。 |
protected | √ | √ | √ | × | 成员变量、方法(不能修饰外部类) | 代码可以在同一个包和子类中访问。 |
默认(无修饰符) | √ | √ | × | × | 类、成员变量、方法 | 该代码只能在同一个包中访问。在您未指定修饰符时使用。 |
private | √ | × | × | × | 成员变量、方法(不能修饰外部类) | 代码只能在声明的类中访问。 |
// public类:可以被所有类访问
public class PublicClass {
public String publicField; // 所有类可访问
protected String protectedField; // 本类、同包类、子类可访问
String defaultField; // 本类、同包类可访问
private String privateField; // 仅本类可访问
public void publicMethod() {} // 所有类可访问
protected void protectedMethod() {}// 本类、同包类、子类可访问
void defaultMethod() {} // 本类、同包类可访问
private void privateMethod() {} // 仅本类可访问
}
**static 关键字**
用于修饰类的成员(变量、方法、内部类),表示属于类本身而非实例。
- 静态变量(类变量)
public class Counter {
// 静态变量:所有对象共享同一个值
private static int count = 0;
// 实例变量:每个对象有独立的值
private int id;
public Counter() {
count++;
id = count;
}
public static int getCount() {
return count;
}
public int getId() {
return id;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.getCount()); // 输出:2(通过类名访问)
System.out.println(c1.getId()); // 输出:1
System.out.println(c2.getId()); // 输出:2
}
}
- 静态方法(类方法)
- 可直接通过类名调用,无需创建对象
- 静态方法中不能使用
this
和super
关键字 - 静态方法只能访问静态成员,不能访问非静态成员
public class MathUtil {
// 静态方法:工具类常用
public static int add(int a, int b) {
return a + b;
}
public static int multiply(int a, int b) {
return a * b;
}
}
// 调用静态方法
int sum = MathUtil.add(3, 5); // 直接通过类名调用
- 静态内部类
public class OuterClass {
private int outerField = 10;
private static int staticOuterField = 20;
// 静态内部类
public static class StaticInnerClass {
public void print() {
// 可以访问外部类的静态成员
System.out.println("静态外部变量: " + staticOuterField);
// 不能直接访问外部类的非静态成员
// System.out.println(outerField); // 编译错误
}
}
// 非静态内部类
public class NonStaticInnerClass {
public void print() {
// 可以访问外部类的所有成员
System.out.println("非静态外部变量: " + outerField);
System.out.println("静态外部变量: " + staticOuterField);
}
}
}
**final 关键字**
表示 “不可改变”,可修饰类、方法、变量。
- 修饰类
被final
修饰的类不能被继承。
// final类不能有子类
public final class FinalClass {
// ...
}
// 编译错误:不能继承final类
// public class SubClass extends FinalClass {}
- 修饰方法
被final
修饰的方法不能被重写。
public class Parent {
// final方法不能被重写
public final void finalMethod() {
System.out.println("这是final方法");
}
}
public class Child extends Parent {
// 编译错误:不能重写final方法
// public void finalMethod() {}
}
- 修饰变量
被final
修饰的变量成为常量,一旦赋值就不能改变。
public class FinalVariableExample {
// 声明时赋值
public final int CONSTANT1 = 100;
// 空白final变量,必须在构造方法中赋值
public final int CONSTANT2;
public FinalVariableExample() {
CONSTANT2 = 200; // 构造方法中赋值
}
public void method() {
final int localConstant = 300;
// localConstant = 400; // 编译错误:不能修改final变量
}
}
**abstract 关键字**
用于声明抽象类和抽象方法。
- 抽象类
- 不能被实例化
- 可以包含抽象方法和非抽象方法
- 抽象类的子类必须实现所有抽象方法(除非子类也是抽象类)
// 抽象类
public abstract class Animal {
// 非抽象方法
public void breathe() {
System.out.println("呼吸空气");
}
// 抽象方法:只有声明,没有实现
public abstract void makeSound();
}
// 继承抽象类并实现抽象方法
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵喵");
}
}
**this 关键字**
表示当前对象的引用,用于:
- 区分成员变量和局部变量
public class Person {
private String name;
public void setName(String name) {
this.name = name; // this.name表示成员变量
}
}
- 调用当前类的其他构造方法
public class Person {
private String name;
private int age;
public Person() {
this("未知", 0); // 调用带参数的构造方法
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
- 返回当前对象
public class Person {
private int age;
public Person setAge(int age) {
this.age = age;
return this; // 返回当前对象,支持方法链调用
}
}
// 方法链调用
Person p = new Person();
p.setAge(20).setName("张三"); // 假设还有setName方法
**super 关键字**
用于访问父类的成员,主要用途:
- 调用父类的构造方法
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // 调用父类的构造方法
this.breed = breed;
}
}
- 调用父类的方法
public class Parent {
public void print() {
System.out.println("父类的print方法");
}
}
public class Child extends Parent {
@Override
public void print() {
super.print(); // 调用父类的print方法
System.out.println("子类的print方法");
}
}
**其他相关关键字**
- **extends 关键字**用于表示类的继承关系,Java 中类只能单继承。
public class Animal {
// ...
}
// Dog类继承Animal类
public class Dog extends Animal {
// 继承了Animal类的属性和方法
// 可以添加自己特有的属性和方法
}
- implements 关键字
用于表示类实现接口,一个类可以实现多个接口。
public interface Runnable {
void run();
}
public interface Swimmable {
void swim();
}
// 实现多个接口
public class Duck implements Runnable, Swimmable {
@Override
public void run() {
System.out.println("鸭子在跑");
}
@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}
- transient 关键字
用于修饰成员变量,表示该变量不会被序列化。
import java.io.Serializable;
public class User implements Serializable {
private String username;
private transient String password; // 不会被序列化
// getter和setter方法
}
- volatile 关键字
用于修饰成员变量,确保多线程环境下变量的可见性。
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void checkFlag() {
while (!flag) {
// 循环等待,直到flag变为true
}
System.out.println("Flag已变为true");
}
}
- synchronized 关键字
方法一次只能被一个线程访问。
1.3 创建对象
在 Java 中,对象是从类创建的。我们已经创建了名为 Main
的类,所以现在我们可以使用它来创建对象。
如需创建 Main
对象,请指定类名,后跟对象名,并使用关键字 new
:
public class Main {
int x = 5;
public static void main(String[] args) {
Main myObj1 = new Main(); // 对象 1
Main myObj2 = new Main(); // 对象 2
System.out.println(myObj1.x);
System.out.println(myObj2.x);
}
}
1.4 使用多个类
还可以创建一个类的对象并在另一个类中访问它。这通常用于更好地组织类(一个类拥有所有属性和方法,而另一个类拥有 main()
方法(要执行的代码))。
请记住,java 的文件名应与类名相匹配。在这个例子中,我们在同一个目录/文件夹中创建了两个文件:
- Main.java
- Second.java
Main.java
public class Main {
int x = 5;
}
Second.java
class Second {
public static void main(String[] args) {
Main myObj = new Main();
System.out.println(myObj.x);
}
}
当两个文件都编译完成后:
C:\Users\Your Name>javac Main.java
C:\Users\Your Name>javac Second.java
运行 Second.java 文件:
C:\Users\Your Name>java Second
将会输出5.
2. Java 类方法
方法是在类中声明的,并且它们用于执行某些操作:
示例:
在 Main 中创建名为 myMethod()
的方法:
public class Main {
static void myMethod() {
System.out.println("Hello World!");
}
public static void main(String[] args) {
myMethod();
}
}
// 输出 "Hello World!"
静态与非静态
经常会看到具有 static
或 public
属性和方法的 Java 程序。
static
方法和 public
_方法_之间差异的例子:
public class Main {
// 静态方法:
static void myStaticMethod() {
System.out.println("无需创建对象即可调用静态方法");
}
// 公共方法:
public void myPublicMethod() {
System.out.println("公共方法必须通过创建对象来调用");
}
// 主方法:
public static void main(String[] args) {
myStaticMethod(); // 调用静态方法
// myPublicMethod(); 这会编译错误
Main myObj = new Main(); // 创建 Main 的对象
myObj.myPublicMethod(); // 调用对象的公共方法
}
}
3. Java 构造函数
Java 中的构造函数是一种用于初始化对象的_特殊方法_。创建类的对象时,会调用构造函数。它可用于设置对象属性的初始值:
请注意,构造函数名称必须_与类名称匹配,并且不能有返回类型_(比如 void
)。
public class Main {
int modelYear;
String modelName;
public Main(int year, String name) {
modelYear = year;
modelName = name;
}
public static void main(String[] args) {
Main myCar = new Main(1969, "Mustang");
System.out.println(myCar.modelYear + " " + myCar.modelName);
}
}
// 输出 1969 Mustang
4. Java 封装
_封装(Encapsulation)_的含义,是确保“敏感”数据对用户隐藏。要实现这一目标,您必须:
- 将类变量/属性声明为
private
- 提供公共 get 和 set 方法来访问和更新
private
变量的值
public class Person {
private String name; // private = 禁止进入
// Getter
public String getName() {
return name;
}
// Setter
public void setName(String newName) {
this.name = newName;
}
}
public class Main {
public static void main(String[] args) {
Person myObj = new Person();
myObj.name = "Bill"; // 错误
System.out.println(myObj.name); // 错误
}
}
get
方法返回变量名的值。
set
方法接受一个参数 (newName
) 并将其分配给 name
变量。this
关键字用于引用当前对象。
但是,由于 name
变量被声明为 private
,我们_无法_从此类外部访问它:
正确访问应该如下:
public class Main {
public static void main(String[] args) {
Person myObj = new Person();
myObj.setName("Bill"); // 将 name 变量的值设置为 "Bill"
System.out.println(myObj.getName());
}
}
// 输出 "Bill"
为什么要封装?
- 更好地控制类属性和方法
- 类属性可以设为_只读(如果只使用
get
方法)或只写_(如果只使用set
方法)- 灵活:程序员可以改变一部分代码而不影响其他部分
- 提高数据安全性
5. Java 包和 API
Java 中的包用于对相关类进行分组。可将其视为_文件目录中的文件夹_。我们使用包来避免名称冲突,并编写更好的可维护代码。
包分为两类:
- 内置包(来自 Java API 的包)
- 用户定义的包(创建您自己的包)
5.1 内置软件包
Java API 是一个预先编写的类库,可以免费使用,包含在 Java 开发环境中。
该库包含用于管理输入、数据库编程等的组件。完整列表可以在 Oracles 网站上找到:
https://docs.oracle.com/javase/8/docs/api/
库分为_包和类_。这意味着您可以导入单个类(及其方法和属性),也可以导入包含属于指定包的所有类的整个包。
如需使用库中的类或包,您需要使用 import
关键字:
语法:
import package.name.Class; // 导入单个类
import package.name.*; // 导入整个包
5.2 导入类
如果导入需要使用的类,例如_用于获取用户输入_的 Scanner
类,请编写以下代码:
实例
import java.util.Scanner;
class MyClass {
public static void main(String[] args) {
Scanner myObj = new Scanner(System.in);
System.out.println("Enter username");
String userName = myObj.nextLine();
System.out.println("Username is: " + userName);
}
}
在上面的例子中,java.util
是一个包,而 Scanner
是 java.util
包的一个类。使用时需要创建该类的对象并使用 Scanner
类文档中的任何可用方法。上面的例子中,使用 nextLine()
方法,该方法用于读取完整行。
5.3 导入包
有很多包可供选择。在前面的实例中,我们使用了 java.util
包中的 Scanner
类。这个包还包含日期和时间工具、随机数生成器和其他实用程序类。
如需导入整个包,请以星号 (*) 结束句子。下例将导入 java.util
包中的所有类:
import java.util.*;
5.4 用户定义的包
如需创建自己的包,您需要了解:Java 使用文件系统目录来存储它们。就像计算机上的文件夹一样:
└── root
└── mypack
└── MyPackageClass.java
如需创建包,请使用 package
关键字:
MyPackageClass.java
package mypack;
class MyPackageClass {
public static void main(String[] args) {
System.out.println("这是我的包!");
}
}
将文件保存为 MyPackageClass.java,并编译它:
C:\Users\Your Name>javac MyPackageClass.java
然后编译包:
C:\Users\Your Name>javac -d . MyPackageClass.java
这会强制编译器创建 “mypack” 包。
d
关键字指定保存类文件的目的地。您可以使用任何目录名称,例如 c:/user (windows),或者,如果您想将包保存在同一目录中,您可以使用点号 “.
”,如上例所示。**注意:**包名要小写,以免与类名冲突。
当我们编译上例中的包时,会创建名为 “mypack” 的新文件夹。
如需运行 MyPackageClass.java 文件,请键入以下内容:
C:\Users\Your Name>java mypack.MyPackageClass
// 输出:这是我的包!
6. Java 继承(子类和超类)
在 Java 中,可以从一个类继承属性和方法到另一个类。将“继承”这个概念分为两类:
- subclass (子类) - 从另一个类继承的类
- superclass(父类) - 被继承的类
如需从类继承,请使用 extends
关键字。
在下例中,Car
类(子类)继承了 Vehicle
类(超类)的属性和方法:
class Vehicle {
protected String brand = "Ford"; // Vehicle 属性
public void honk() { // Vehicle 方法
System.out.println("Tuut, tuut!");
}
}
class Car extends Vehicle {
private String modelName = "Mustang"; // Car 属性
public static void main(String[] args) {
// 创建 myCar 对象
Car myCar = new Car();
// 调用 myCar 对象上的 honk() 方法(来自 Vehicle 类)
myCar.honk();
// 显示 brand 属性的值(来自 Vehicle 类)和来自 Car 类的 modelName 的值
System.out.println(myCar.brand + " " + myCar.modelName);
}
}
是否注意到 Vehicle 中的
protected
修饰符?将 Vehicle 中的 brand 属性设置为
protected
访问修饰符。如果将其设置为private
,则 Car 类将无法访问它。为什么以及何时使用“继承”?
- 它对于代码可重用性很有用:请在创建新类时重用现有类的属性和方法。
final 关键词
如果不希望其他类从某个类继承,使用 final
关键字:
如果尝试访问 final
类,Java 将生成错误:
final class Vehicle {
...
}
class Car extends Vehicle {
...
}
输出将是这样的:
Main.java:9: error: cannot inherit from final Vehicle
class Main extends Vehicle {
^
1 error)
7. Java 多态
多态(Polymorphism)的意思是“多种形式”,它发生在我们有许多通过继承相互关联的类时。_继承让我们从另一个类继承属性和方法。多态_使用这些方法来执行不同的任务。这允许我们以不同的方式执行单一操作。
例如:名为 Animal
的超类,它有一个名为 animalSound()
的方法。Animals 的子类可以是 Pigs、Cats、Dogs、Birds - 而且它们也有自己的动物声音实现(猪的 oinks 和猫的 meows 等):
class Animal {
public void animalSound() {
System.out.println("The animal makes a sound");
}
}
class Pig extends Animal {
public void animalSound() {
System.out.println("The pig says: wee wee");
}
}
class Dog extends Animal {
public void animalSound() {
System.out.println("The dog says: bow wow");
}
}
class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal(); // 创建 Animal 对象
Animal myPig = new Pig(); // 创建 Pig 对象
Animal myDog = new Dog(); // 创建 Dog 对象
myAnimal.animalSound();
myPig.animalSound();
myDog.animalSound();
}
}
8. Java 内部类
8.1 Java 内部类
在 Java 中,也可以嵌套类(类中的类)。嵌套类的目的是将属于一起的类进行分组,这会使您的代码更具可读性和可维护性。
如需访问内部类,先创建外部类的对象,然后再创建内部类的对象:
class OuterClass {
int x = 10;
class InnerClass {
int y = 5;
}
}
public class Main {
public static void main(String[] args) {
OuterClass myOuter = new OuterClass();
OuterClass.InnerClass myInner = myOuter.new InnerClass();
System.out.println(myInner.y + myOuter.x);
}
}
// 输出 15 (5 + 10)
8.2 私有内部类
与“常规”类不同,内部类可以是 private
或 protected
。如果您不希望外部对象访问内部类,请将该类声明为 private
:
class OuterClass {
int x = 10;
private class InnerClass {
int y = 5;
}
}
public class Main {
public static void main(String[] args) {
OuterClass myOuter = new OuterClass();
OuterClass.InnerClass myInner = myOuter.new InnerClass();
System.out.println(myInner.y + myOuter.x);
}
}
从外部访问私有内部类会发生错误:
Main.java:13: error: OuterClass.InnerClass has private access in OuterClass
OuterClass.InnerClass myInner = myOuter.new InnerClass();
8.3 静态内部类
内部类也可以是 static
,这意味着您可以在不创建外部类的对象的情况下访问它:
class OuterClass {
int x = 10;
static class InnerClass {
int y = 5;
}
}
public class Main {
public static void main(String[] args) {
OuterClass.InnerClass myInner = new OuterClass.InnerClass();
System.out.println(myInner.y);
}
}
// 输出 5
8.4 从内部类访问外部类
内部类的优点之一是它们可以访问外部类的属性和方法:
class OuterClass {
int x = 10;
class InnerClass {
public int myInnerMethod() {
return x;
}
}
}
public class Main {
public static void main(String[] args) {
OuterClass myOuter = new OuterClass();
OuterClass.InnerClass myInner = myOuter.new InnerClass();
System.out.println(myInner.myInnerMethod()); // 直接调用内部类的方法返回了外部类的属性
}
}
// 输出 10
9. Java 抽象
数据_抽象_是隐藏某些细节并仅向用户显示基本信息的过程。
抽象可以通过_抽象类_或接口来实现
abstract
关键字是一个非访问修饰符,用于类和方法:
- 抽象类:是一种受限制的类,不能用于创建对象(要访问它,它必须从另一个类继承)。
- 抽象方法:只能在抽象类中使用,并且没有主体。主体由子类提供(继承自)。
抽象类可以同时拥有抽象方法和常规方法:
abstract class Animal {
public abstract void animalSound();
public void sleep() {
System.out.println("Zzz");
}
}
在上例中,无法创建 Animal 类的对象:
Animal myObj = new Animal(); // 会产生错误
如需访问抽象类,它必须从另一个类继承。
// 抽象类:
abstract class Animal {
// 抽象方法(没有主体):
public abstract void animalSound();
// 常规方法:
public void sleep() {
System.out.println("Zzz");
}
}
// 子类(继承自 Animal):
class Pig extends Animal {
public void animalSound() {
// 此处提供了 animalSound() 的主体
System.out.println("The pig says: wee wee");
}
}
class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // 创建 Pig 对象
myPig.animalSound(); // 打印: The pig says: wee wee
myPig.sleep(); // 打印: Zzz
}
}
10. Java 接口
在 Java 中实现抽象的另一种方法是使用接口。
interface
是一种完全_“抽象类”_,用于组合相关方法与空主体:
// 接口:
interface Animal {
public void animalSound(); // 接口方法(没有主体)
public void run(); // 接口方法(没有主体)
}
如需访问接口方法,接口必须由另一个类使用 implements
关键字(而不是 extends
)来“实现”(有点像继承)。
// 接口:
interface Animal {
public void animalSound(); // 接口方法(没有主体)
public void sleep(); // 接口方法(没有主体)
}
// Pig “实现”了 Animal 接口:
class Pig implements Animal {
public void animalSound() {
// 此处提供了 animalSound() 的主体
System.out.println("The pig says: wee wee");
}
public void sleep() {
// 此处提供了 sleep() 的主体
System.out.println("Zzz");
}
}
class Main {
public static void main(String[] args) {
Pig myPig = new Pig(); // 创建 Pig 对象
myPig.animalSound();
myPig.sleep();
}
}
接口注意事项:
- 与_抽象类一样,接口不能_用于创建对象(在上面的例子中,不可能在 MyMainClass 中创建 “Animal” 对象)
- 接口方法没有主体 - 主体由 “implement” 类提供
- 在实现接口时,您必须覆盖其所有方法
- 接口方法默认是
abstract
和public
- 接口属性默认为
public
、static
和final
- 接口不能包含构造函数(因为它不能用于创建对象)
为什么以及何时使用接口?
1)为了实现安全 - 隐藏某些细节,只显示一个对象(接口)的重要细节。
2)Java 不支持“多重继承”(一个类只能从一个超类继承)。但是,它可以通过接口来实现,因为类可以_实现_ 多个接口。
**注释:**如需实现多个接口,用逗号分隔它们(请见下面的例子)。
多个接口
要实现多个接口,请用逗号分隔它们:
interface FirstInterface {
public void myMethod(); // 接口方法
}
interface SecondInterface {
public void myOtherMethod(); // 接口方法
}
class DemoClass implements FirstInterface, SecondInterface {
public void myMethod() {
System.out.println("Some text..");
}
public void myOtherMethod() {
System.out.println("Some other text...");
}
}
class Main {
public static void main(String[] args) {
DemoClass myObj = new DemoClass();
myObj.myMethod();
myObj.myOtherMethod();
}
}
11. Java 枚举
11.1 枚举
枚举(enum
)是一种特殊的“类”,代表一组_常量_(不可更改的变量,如 final
变量)。
如需创建 enum
,请使用 enum
关键字(而不是类或接口),并用逗号分隔常量。请注意,它们应为大写字母:
enum Level {
LOW,
MEDIUM,
HIGH
}
使用_点语法_访问 enum
常量:
Level myVar = Level.MEDIUM;
11.2 类中的枚举
public class Main {
enum Level {
LOW,
MEDIUM,
HIGH
}
public static void main(String[] args) {
Level myVar = Level.MEDIUM;
System.out.println(myVar);
}
}
// 输出:MEDIUM
11.3 Switch 语句中的枚举
枚举通常用于 switch
语句中,以检查相应的值:
enum Level {
LOW,
MEDIUM,
HIGH
}
public class Main {
public static void main(String[] args) {
Level myVar = Level.MEDIUM;
switch(myVar) {
case LOW:
System.out.println("低级");
break;
case MEDIUM:
System.out.println("中级");
break;
case HIGH:
System.out.println("高级");
break;
}
}
}
// 输出:中级
11.4 遍历枚举
枚举类型有一个 values()
方法,它返回包含所有枚举常量的数组。当您想遍历枚举的常量时,此方法很有用:
for (Level myVar : Level.values()) {
System.out.println(myVar);
}
// 输出:LOW
// MEDIUM
// HIGH
枚举和类的区别
enum
可以像class
一样拥有属性和方法。唯一的区别是枚举常量是public
、static
和final
(不可更改 - 不能被覆盖)。
enum
不能用于创建对象,也不能扩展其他类(但它可以实现接口)。为什么以及何时使用枚举?
当您知道某些值不会改变时请使用枚举,例如月、日、颜色、一副牌等。
12. Java 用户输入 (Scanner)
Scanner
类用于获取用户输入,它位于 java.util
包中。如需使用 Scanner
类,请创建该类的对象并使用 Scanner
类文档中的任何可用方法。输入类型表如下:
方法 | 描述 |
---|---|
nextBoolean() |
读取来自用户的 boolean 值。 |
nextByte() |
读取来自用户的 byte 值。 |
nextDouble() |
读取来自用户的 double 值。 |
nextFloat() |
读取来自用户的 float 值。 |
nextInt() |
读取来自用户的 int 值。 |
nextLine() |
读取来自用户的 String 值。 |
nextLong() |
读取来自用户的 long 值。 |
nextShort() |
读取来自用户的 short 值。 |
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner myObj = new Scanner(System.in);
System.out.println("请输入名字、年龄、薪资:");
// 字符串输入
String name = myObj.nextLine();
// 数值输入
int age = myObj.nextInt();
double salary = myObj.nextDouble();
// 输出用户的输入
System.out.println("名字:" + name);
System.out.println("年龄:" + age);
System.out.println("薪资:" + salary);
}
}
13. Java 日期和时间
Java 没有内置的 Date 类,但我们可以导入 java.time
包来使用日期和时间 API。该软件包囊括了许多日期和时间类。例如:
类 | 描述 |
---|---|
LocalDate |
表示日期(年、月、日(yyyy-MM-dd))。 |
LocalTime |
表示时间(小时、分钟、秒和纳秒 (HH-mm-ss-ns))。 |
LocalDateTime |
表示日期和时间 (yyyy-MM-dd-HH-mm-ss-ns)。 |
DateTimeFormatter |
用于显示和解析日期时间对象的格式化程序。 |
13.1 显示当前日期
如需显示当前日期,请导入 java.time.LocalDate
类,并使用其 now()
方法:
import java.time.LocalDate; // 导入 LocalDate 类
public class Main {
public static void main(String[] args) {
LocalDate myObj = LocalDate.now(); // 创建日期对象
System.out.println(myObj); // 显示当前日期
}
}
// 输出: 2025-07-27
13.2 显示当前时间
如需显示当前时间(小时、分钟、秒和纳秒),请导入 java.time.LocalTime
类,并使用其 now()
方法:
import java.time.LocalTime; // 导入 LocalTime 类
public class Main {
public static void main(String[] args) {
LocalTime myObj = LocalTime.now();
System.out.println(myObj);
}
}
// 输出: 18:51:44.890974600
13.3 显示当前日期和时间
如需显示当前日期和时间,请导入 java.time.LocalDateTime
类,并使用其 now()
方法:
import java.time.LocalDateTime; // 导入 LocalDateTime 类
public class Main {
public static void main(String[] args) {
LocalDateTime myObj = LocalDateTime.now();
System.out.println(myObj);
}
}
// 输出: 2025-07-27T18:52:39.207533500
13.4 格式化日期和时间
上例中的 “T” 用于将日期与时间分开。您可以使用 DateTimeFormatter
类和同一包中的 ofPattern()
方法来格式化或解析日期时间对象。下例将从日期时间中删除 “T” 和纳秒:
import java.time.LocalDateTime; // 导入 LocalDateTime 类
import java.time.format.DateTimeFormatter; // 导入 DateTimeFormatter 类
public class Main {
public static void main(String[] args) {
LocalDateTime myDateObj = LocalDateTime.now();
System.out.println("格式化之前:" + myDateObj);
DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
String formattedDate = myDateObj.format(myFormatObj);
System.out.println("格式化之后:" + formattedDate);
}
}
// 格式化之前:2025-07-27T18:53:41.060494900
// 格式化之后:27-07-2025 18:53:41
ofPattern()
方法接受各种值,如果您想以不同的格式显示日期和时间。例如:
值 | 例子 |
---|---|
yyyy-MM-dd | “1988-09-29” |
dd/MM/yyyy | “29/09/1988” |
dd-MMM-yyyy | “29-Sep-1988” |
E, MMM dd yyyy | “Thu, Sep 29 1988” |
六、Java 数据结构
1. 包装类
1.1 先明确两个关键概念
- **基本数据类型(Primitive Types)**Java 有 8 种基本类型:
int
、byte
、short
、long
、float
、double
、char
、boolean
。特点:直接存储值(而非地址),不继承Object
类,不能调用方法,存储在栈内存中。 - **引用数据类型(Reference Types)**包括:类(如
String
、Integer
)、接口、数组、枚举等。特点:存储的是对象的地址(而非值本身),继承Object
类,可调用方法,对象实体存储在堆内存中。
Java中的集合框架(如List
、Set
、Map
等)对存储类型的支持规则不同,但共同遵循一个原则:引用类型可以直接存储,基本类型需特殊处理。
1.2 为什么**String
**可以直接使用?
String
是引用类型(本质是String
类的对象),无论在数组还是列表中,都符合容器对 “存储引用类型” 的要求。
1.3 为什么其他基本类型(如**int
)需要用Integer
****?**
以int
为例,它是基本类型,而 Java 的集合框架(如****List
、Set
****)只能存储引用类型,不能直接存储基本类型。因此需要用对应的 “包装类”(Integer
)来包装基本类型,使其成为引用类型。
1.4 包装类的作用:连接基本类型和引用类型
在使用非 String 类型时,必须指定等效的包装类:Integer
原始数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
2. ArrayList(数组)
ArrayList
类是一种可调整大小的数组,可以在 java.util
包中找到。
Java 中的内置数组和 ArrayList
的区别在于,数组的大小不能修改(如果要向数组添加元素或从数组中删除元素,则必须创建一个新数组)。虽然可以随时在 ArrayList
中添加和删除元素。语法也略有不同。
以下表格列出了 ArrayList 的所有方法。
部分方法使用 ArrayList 中元素的类型作为参数或返回值。在表格中,这种类型将被表示为 T。
方法 | 描述 | 返回类型 |
---|---|---|
add() | 向列表中添加一个元素。 | boolean |
addAll() | 向列表中添加一个集合的所有元素。 | boolean |
clear() | 从列表中移除所有元素。 | void |
clone() | 创建 ArrayList 的一个副本。 | Object |
contains() | 检查列表中是否包含某个元素。 | boolean |
ensureCapacity() | 增加列表的容量,以便能够容纳指定数量的元素。 | void |
forEach() | 对列表中的每个元素执行一个操作。 | void |
get() | 返回列表中指定位置的元素。 | T |
indexOf() | 返回列表中首次出现某个元素的索引。 | int |
isEmpty() | 检查列表是否为空。 | boolean |
iterator() | 为 ArrayList 返回 Iterator 对象。 |
Iterator |
lastIndexOf() | 返回列表中最后一次出现某个元素的索引。 | int |
listIterator() | 为 ArrayList 返回 ListIterator 对象。 |
ListIterator |
remove() | 从列表中移除一个元素。 | boolean |
removeAll() | 从列表中移除一个集合的所有元素。 | boolean |
removeIf() | 移除列表中满足指定条件的所有元素。 | boolean |
replaceAll() | 将列表中的每个元素替换为对该元素执行操作后的结果。 | void |
retainAll() | 从列表中移除所有不属于指定集合的元素。 | boolean |
set() | 替换列表中指定位置的元素。 | T |
size() | 返回列表中的元素数量。 | int |
sort() | 对列表进行排序。 | void |
spliterator() | 为 ArrayList 返回 Spliterator 对象。 |
Spliterator |
subList() | 返回列表的子列表,该子列表提供了对原列表中一定范围内元素的访问。 | List |
toArray() | 返回包含列表中所有元素的数组。 | Object[] |
trimToSize() | 根据需要减小列表的容量以匹配元素的数量。 | void |
示例:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> cars = new ArrayList<String>();
cars.add("Volvo"); // 添加元素
cars.add(0, "Mazda"); // 在列表开头(索引0)插入元素
String temp_car = cars.get(0); // 访问元素
cars.set(1, "BWM"); // 修改指定索引的元素
cars.remove(0); // 删除指定索引的元素
cars.clear(); // 删除所有元素
int size = cars.size(); // 获取数组长度
// for 循环遍历数组
for (int i = 0; i < cars.size(); i++) {
System.out.println(cars.get(i));
}
// for-each 循环遍历数组
for (String i : cars) {
System.out.println(i);
}
System.out.println(size);
System.out.println(temp_car);
System.out.println(cars);
}
}
对数组进行排序
java.util
包中另一个有用的类是 Collections
类,它包括 sort()
方法,用于按字母或数字对列表进行排序:
import java.util.ArrayList;
import java.util.Collections; // 导入 Collections 类
public class Main {
public static void main(String[] args) {
ArrayList<Integer> myNumbers = new ArrayList<Integer>(); //注意非String对象的数据类型必须为Integer
myNumbers.add(33);
myNumbers.add(15);
myNumbers.add(20);
myNumbers.add(34);
myNumbers.add(8);
myNumbers.add(12);
Collections.sort(myNumbers); // 对 myNumbers 进行升序排序
// Collections.sort(myNumbers, Collections.reverseOrder()); // 对 myNumbers 进行逆序排序
for (int number : myNumbers) {
System.out.println(number);
}
}
}
3. LinkedList(链表)
以下表格列出了 LinkedList 的所有方法。
部分方法使用 LinkedList 中元素的类型作为参数或返回值。在表格中,这种类型将被表示为 T。
方法 | 描述 | 返回类型 |
---|---|---|
add() | 向列表中添加一个元素。 | boolean |
addAll() | 向列表中添加一个集合的所有元素。 | boolean |
addFirst() | 在列表开头添加一个元素。 | void |
addLast() | 在列表末尾添加一个元素。 | void |
clear() | 从列表中移除所有元素。 | void |
clone() | 创建 LinkedList 的副本。 | |
contains() | 检查列表中是否包含某个元素。 | boolean |
descendingIterator() | 返回一个迭代器,以逆序遍历列表中的元素。 | Iterator |
element() | 检索列表中的第一个元素。与 getFirst() 类似。 |
T |
forEach() | 对列表中的每个元素执行一个操作。 | void |
get() | 返回列表中指定位置的元素。 | T |
getFirst() | 返回列表中的第一个元素。 | T |
getLast() | 返回列表中的最后一个元素。 | T |
indexOf() | 返回列表中首次出现某个元素的索引。 | int |
isEmpty() | 检查列表是否为空。 | boolean |
iterator() | 为 LinkedList 返回一个 Iterator 对象。 |
Iterator |
lastIndexOf() | 返回列表中最后一次出现某个元素的索引。 | int |
listIterator() | 为 LinkedList 返回一个 ListIterator 对象。 |
ListIterator |
offer() | 在列表末尾添加一个元素。 | |
offerFirst() | 在列表开头添加一个元素。 | boolean |
offerLast() | 在列表末尾添加一个元素。 | boolean |
peek() | 检索但不移除列表中的第一个元素。与 getFirst() 类似。 |
T |
peekFirst() | 检索但不移除列表中的第一个元素。与 peek() 类似。 |
T |
peekLast() | 检索但不移除列表中的最后一个元素。 | T |
poll() | 检索并移除列表中的第一个元素。与 removeFirst() 类似 |
T |
pollFirst() | 检索并移除列表中的第一个元素。与 poll() 类似。 |
T |
pollLast() | 检索并移除列表中的最后一个元素。 | T |
pop() | 返回并移除列表中的第一个元素。与 removeFirst() 类似 |
T |
push() | 在列表开头添加一个元素。与 addFirst() 类似。 |
void |
remove() | 从列表中移除一个元素。 | boolean |
removeAll() | 从列表中移除一个集合的所有元素。 | boolean |
removeFirst() | 移除列表中的第一个元素。 | T |
removeFirstOccurrence() | 移除列表中第一次出现的指定元素。 | |
removeIf() | 移除列表中满足指定条件的所有元素。 | boolean |
removeLast() | 移除列表中的最后一个元素。 | T |
removeLastOccurrence() | 移除列表中最后一次出现的指定元素。 | boolean |
replaceAll() | 将列表中的每个元素替换为对该元素执行操作后的结果。 | void |
retainAll() | 从列表中移除所有不属于指定集合的元素。 | boolean |
set() | 替换列表中指定位置的元素。 | T |
size() | 返回列表中的元素数量。 | int |
sort() | 对列表进行排序。 | void |
spliterator() | 为 LinkedList 返回 Spliterator 对象。 |
Spliterator |
subList() | 返回列表的子列表,该子列表提供了对原列表中一定范围内元素的访问。 | List |
toArray() | 返回包含列表中所有元素的数组。 | Object[] |
示例:
// 导入 LinkedList 类
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> cars = new LinkedList<String>();
cars.add("Volvo");
cars.add("BMW");
cars.add("Ford");
cars.add("Mazda");
System.out.println(cars);
}
}
ArrayList 与 LinkedList
LinkedList
类是一种集合,它可以包含许多相同类型的对象,就像ArrayList
一样。
LinkedList
类拥有与ArrayList
类相同的所有方法,因为它们都实现了List
接口。这意味着您可以以相同的方式添加项目、更改项目、删除项目和清除列表。然而,虽然
ArrayList
类和LinkedList
类能够以相同的方式使用,但它们的构建方式却大不相同。ArrayList 的工作原理
ArrayList
类内部有一个常规数组。添加元素时,将其放入数组中。如果数组不够大,则会创建一个新的更大的数组来替换旧数组,然后删除旧数组。LinkedList 的工作原理
LinkedList
将其项目存储在“容器”中。该列表有一个指向第一个容器的链接,每个容器都有一个指向列表中下一个容器的链接。要将元素添加到列表中,需要将该元素放入一个新容器中,并将该容器链接到列表中的其他容器之一。何时使用
请使用
ArrayList
存储和访问数据,使用LinkedList
操作数据。
4. HashMap
HashMap
用来存储键值对。它可以存储不同的类型:String
键和 Integer
值,或相同的类型,如:String
键和 String
值:
4.1 创建 HashMap
创建名为 capitalCities 的 HashMap
对象,它将存储 String
_键_和 String
值:
import java.util.HashMap; // 导入 HashMap 类
HashMap<String, String> capitalCities = new HashMap<String, String>();
4.2 添加项目
向其中添加项目,使用 put()
方法:
// 导入 HashMap 类
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
// 创建名为 capitalCities 的 HashMap 对象
HashMap<String, String> capitalCities = new HashMap<String, String>();
// Add keys and values (Country, City)
capitalCities.put("China", "Beijing");
capitalCities.put("England", "London");
capitalCities.put("Germany", "Berlin");
capitalCities.put("USA", "Washington DC");
System.out.println(capitalCities);
}
}
4.3 访问项目
如需访问 HashMap
中的值,使用 get()
方法并引用其键:
capitalCities.get("England")
4.4 删除项目
如需删除项目,使用 remove()
方法并引用键:
capitalCities.remove("England")
如需删除所有项目,使用 clear()
方法:
capitalCities.clear();
4.5 哈希图大小
如需找出有多少项目,使用 size()
方法:
capitalCities.size();
4.6 循环遍历 HashMap
使用 for-each 循环遍历 HashMap
的项目。
**注释:**如果您只需要键,请使用 keySet()
方法,如果您只需要值,请使用 values()
方法:
// 打印键
for (String i : capitalCities.keySet()) {
System.out.println(i);
}
// 打印值
for (String i : capitalCities.values()) {
System.out.println(i);
}
// 打印键和值
for (String i : capitalCities.keySet()) {
System.out.println("key: " + i + " value: " + capitalCities.get(i));
}
5. HashSet
HashSet 是项目的集合,其中每个项目都是唯一的,可以在 java.util
包中找到:
5.1 创建HashSet
创建名为 cars 的 HashSet
对象,用于存储字符串:
import java.util.HashSet; // 导入 HashSet 类
HashSet<String> cars = new HashSet<String>();
5.3 添加项目
要向其中添加项目,使用 add()
方法:
// 导入 HashSet 类
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
HashSet<String> cars = new HashSet<String>();
cars.add("Volvo");
cars.add("BMW");
cars.add("Ford");
cars.add("BMW");
cars.add("Mazda");
System.out.println(cars);
}
}
5.4 检查项目是否存在
如需检查 HashSet 中是否存在项目,使用 contains()
方法:
cars.contains("Mazda");
5.5 删除项目
如需删除项目,使用 remove()
方法:
cars.remove("Volvo");
如需删除所有项目,使用 clear()
方法:
cars.clear()
5.6 HashSet 大小
如需找出有多少项,使用 size
方法:
cars.size()
5.7 循环遍历 HashSet
for (String i : cars) {
System.out.println(i);
}
6. Iterator 迭代器
迭代器(Iterator
)是一种对象,可用于循环遍历集合,比如 ArrayList 和 HashSet。
要使用迭代器,必须从 java.util
包中导入它。
6.1 获取迭代器
iterator()
方法可用于获取任何集合的 Iterator
:
// 导入 ArrayList 类和 Iterator 类:
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
// 生成集合:
ArrayList<String> cars = new ArrayList<String>();
cars.add("Volvo");
cars.add("BMW");
cars.add("Ford");
cars.add("Mazda");
// 获取迭代器:
Iterator<String> it = cars.iterator(); //此时迭代器的初始状态是指向集合中第一个元素的 “前面”
// 打印首个项目:
System.out.println(it.next());
}
}
6.2 循环遍历集合
如需循环遍历集合,使用 Iterator
的 hasNext()
和 next()
方法:
while(it.hasNext()) {
System.out.println(it.next());
}
6.3 从集合中删除项目
迭代器旨在轻松更改它们循环遍历的集合。remove()
方法可以在循环时从集合中删除项目。
示例:使用迭代器从集合中删除小于 10 的数字:
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(12);
numbers.add(8);
numbers.add(2);
numbers.add(23);
Iterator<Integer> it = numbers.iterator();
while(it.hasNext()) { // it.hasNext()用于检查集合中是否还有下一个元素。
Integer i = it.next();
if(i < 10) {
it.remove();
}
}
System.out.println(numbers);
}
}
**注意:**尝试使用 _for 循环_或 _for-each 循环_删除项目将无法正常工作,因为在代码尝试循环的同时,集合正在更改大小。
七、Java 高级
1. 异常
在执行 Java 代码时,可能会发生各种错误:程序员的编码错误、错误输入导致的异常或其他不可预见的情况。当发生错误时,Java 通常会停止并生成错误消息。用技术术语讲就是:Java 将抛出_异常_(抛出错误)。
1.1 Try…Catch
try
语句允许您定义要在执行时测试错误的代码块。
如果try
块中发生错误,catch
语句允许您定义要执行的代码块。
try
和 catch
关键字成对出现:
语法:
try {
// 要尝试的代码块
}
catch(Exception e) {
// 处理错误的代码块
}
示例:
public class Main {
public static void main(String[ ] args) {
try {
int[] myNumbers = {1, 2, 3};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("出错了。");
}
}
}
// 输出: 出错了
1.2 Finally
finally
语句让你在 try...catch
之后执行代码,不管结果如何:
public class Main {
public static void main(String[] args) {
try {
int[] myNumbers = {1, 2, 3};
System.out.println(myNumbers[10]);
} catch (Exception e) {
System.out.println("出错了。");
} finally {
System.out.println("'try catch' 已结束。");
}
}
}
// 输出:
// 出错了。
// 'try catch' 已结束。
1.3 throw 关键字
throw
语句允许您创建自定义错误。
throw
语句可与异常类型一同使用。Java 中有许多可用的_异常类型_:
ArithmeticException
:算术异常,当出现数学运算错误时抛出。最常见的情况是整数除以零(int a = 5 / 0;
),因为这在数学上是无意义的操作。FileNotFoundException
:文件未找到异常,属于 IO 异常的一种。当程序试图打开一个不存在的文件(如通过FileInputStream
读取文件),且该操作未被正确捕获处理时抛出。ArrayIndexOutOfBoundsException
:数组索引越界异常。当访问数组时使用了超出其有效范围的索引(如数组长度为 3,却访问索引3
或-1
)时抛出,因为数组索引从0
开始,最大索引为 “长度 - 1”。SecurityException
:安全异常。当程序尝试执行一个被安全管理器(SecurityManager)禁止的操作时抛出,例如访问受限资源、修改系统配置等,常见于有安全限制的环境(如 Applet)。
实例
如果_年龄_低于 18 岁,则抛出异常(打印 “拒绝访问”)。如果年龄为 18 岁或以上,则打印 “允许访问”:
public class Main {
static void checkAge(int age) {
if (age < 18) {
throw new ArithmeticException("拒绝访问 - 您需要至少满 18 岁。");
}
else {
System.out.println("允许访问 - 您的年纪够大!");
}
}
public static void main(String[] args) {
checkAge(15); // 将年龄设置为 15(低于 18 ...)
}
}
// 输出:
Exception in thread "main" java.lang.ArithmeticException: 拒绝访问 - 您需要至少满 18 岁。
at Main.checkAge(Main.java:4)
at Main.main(Main.java:12)
如果年龄是 20 岁,则不会出现异常:
2. 正则表达式 - RegEx
正则表达式是形成搜索模式的字符序列。在文本中搜索数据时,您可以使用搜索模式来描述您要搜索的内容。
正则表达式可以是单个字符,也可以是更复杂的模式。
正则表达式可用于执行所有类型的_文本搜索和文本替换_操作。
Java 没有内置的正则表达式类,但我们可以导入 java.util.regex
包来处理正则表达式。该包提供以下类:
Pattern
类 - 定义模式(用于搜索)Matcher
类 - 用于搜索模式PatternSyntaxException
类 - 指示正则表达式模式中的语法错误
实例
找出句子中是否出现 “joey” 一词:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Pattern pattern = Pattern.compile("joey", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("Visit joey!");
boolean matchFound = matcher.find();
if(matchFound) {
System.out.println("找到匹配");
} else {
System.out.println("未找到匹配");
}
}
}
// 输出:找到匹配
解释:此例在句子中搜索单词 “joey”。
首先,使用 Pattern.compile()
方法创建模式。第一个参数规定要搜索的模式,第二个参数是一个标志,规定搜索应该不区分大小写。第二个参数是可选的。
matcher()
方法用于在字符串中搜索模式。它返回 Matcher 对象,其中包含有关已执行搜索的信息。
如果在字符串中找到模式,则 find()
方法返回 true,如果未找到,则返回 false。
标志
compile()
方法中的标志改变了搜索的执行方式。这是其中几个:
Pattern.CASE_INSENSITIVE
- 执行搜索时将忽略字母的大小写。Pattern.LITERAL
- 模式中的特殊字符没有任何特殊含义,在执行搜索时将被视为普通字符。Pattern.UNICODE_CASE
- 将它与 CASE_INSENSITIVE 标志一起使用也可以忽略英文字母表之外的字母的大小写
正则表达式模式
Pattern.compile()
方法的第一个参数是模式。它描述了正在搜索的内容。
括号用于查找一系列字符:
表达式 | 描述 |
---|---|
[abc] | 查找括号之间的一个字符。 |
[^abc] | 查找不在括号之间的一个字符。 |
[0-9] | 查找范围为 0 到 9 的一个字符。 |
元字符
元字符是拥有特殊含义的字符:
元字符 | 描述 |
---|---|
. | 查找任何字符的一个实例。 |
^ | 在字符串的开头查找匹配,如:^Hello。 |
$ | 在字符串的结尾查找匹配,如: World$。 |
\d | 查找数字。 |
\s | 查找空白字符。 |
\b | 在单词开头查找匹配,比如:\bWORD,或在单词结尾,比如:WORD\b。 |
\u_xxxx_ | 查找十六进制数 xxxx 指定的 Unicode 字符。 |
量词
量词定义数量:
量词 | 描述 |
---|---|
n+ | 匹配包含至少一个 n 的任意字符串。 |
n* | 匹配包含零个或多个 n 的任意字符串。 |
n? | 匹配包含零个或一个 n 的任意字符串。 |
n{x} | 匹配包含 x 个 n 的序列的任意字符串。 |
n{x,y} | 匹配包含从 x 到 y 个 n 的序列的任意字符串。 |
n{x,} | 匹配包含至少 x 个 n 的序列的任意字符串。 |
**注意:**如果您的表达式需要搜索特殊字符之一,可以使用反斜杠 ( \ ) 将其转义。在 Java 中,字符串中的反斜杠需要自己转义,因此需要两个反斜杠来转义特殊字符。例如,要搜索一个或多个问号,您可以使用以下表达式:“\?”
3. 线程
3.1 创建线程
创建线程有两种方法。
可通过扩展 Thread
类并覆盖其 run()
方法来创建:
扩展语法
public class Main extends Thread {
public void run() {
System.out.println("这段代码在一个线程中运行");
}
}
另一种创建线程的方法是实现 Runnable
接口:
实现语法
public class Main implements Runnable {
public void run() {
System.out.println("这段代码在一个线程中运行");
}
}
3.2 运行线程
如果类扩展了 Thread
类,则可以通过创建该类的实例并调用其 start()
方法来运行该线程:
扩展实例
public class Main extends Thread {
public static void main(String[] args) {
Main thread = new Main();
thread.start();
System.out.println("这段代码在线程之外");
}
public void run() {
System.out.println("这段代码在一个线程中运行");
}
}
如果类实现了 Runnable
接口,则可以通过将类的实例传递给 Thread
对象的构造函数,然后调用线程的 start()
方法来运行线程:
实现实例
public class Main implements Runnable {
public static void main(String[] args) {
Main obj = new Main();
Thread thread = new Thread(obj);
thread.start();
System.out.println("这段代码在线程之外");
}
public void run() {
System.out.println("这段代码在一个线程中运行");
}
}
“扩展”和“实现”线程之间的差异
主要区别在于,当类扩展 Thread 类时,您不能扩展任何其他类,但通过实现 Runnable 接口,也可以从另一个类扩展,例如:类
MyClass extends OtherClass implements Runnable
。
3.3 并发问题
由于线程与程序的其他部分同时运行,因此无法获知代码将以何种顺序运行。当线程和主程序读取和写入相同的变量时,值是不可预测的。由此产生的问题称为并发问题。
在下面的例子中,变量 amount 是不可预测的:
public class Main extends Thread {
public static int amount = 0;
public static void main(String[] args) {
Main thread = new Main();
thread.start();
System.out.println(amount);
amount++;
System.out.println(amount);
}
public void run() {
amount++;
}
}
为了避免并发问题,最好在线程之间共享尽可能少的属性。如果需要共享属性,一种可能的解决方案是使用线程的 isAlive()
方法在使用线程可以更改的任何属性之前检查线程是否已完成运行。
使用 isAlive()
来防止并发问题:
public class Main extends Thread {
public static int amount = 0;
public static void main(String[] args) {
Main thread = new Main();
thread.start();
// Wait for the thread to finish
while(thread.isAlive()) {
System.out.println("Waiting...");
}
// Update amount and print its value
System.out.println("Main: " + amount);
amount++;
System.out.println("Main: " + amount);
}
public void run() {
amount++;
}
}
4. Lambda 表达式
Lambda 表达式是在 Java 8 中添加的。
lambda 表达式是一小段代码,它接受参数并返回值。 Lambda 表达式类似于方法,但不需要名称,并可直接在方法主体中实现。
语法
最简单的 lambda 表达式包含一个参数和一个表达式:
parameter -> expression
如需使用多个参数,请将它们括在括号中:
(parameter1, parameter2) -> expression
表达式(expression)是有限制的。它们必须立即返回值,且不能包含变量、赋值或诸如 if
或 for
之类的语句。
为了进行更复杂的操作,可以在花括号内使用代码块:
(parameter1, parameter2) -> { code block }
如果 lambda 表达式需要返回值,那么代码块内需要有 return
语句。
示例:在 ArrayList
的 forEach()
方法中使用 Lamba 表达式来打印列表中的每个项目:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);
numbers.forEach( (n) -> { System.out.println(n); } );
}
}
5. 高级排序(Comparator 和 Comparable)
在“列表排序”章节中,您学习了如何按字母顺序和数字顺序对列表进行排序,但如果列表中包含对象呢?
为了对对象进行排序,您需要指定一个规则来决定对象的排序方式。例如,如果您有一个汽车列表,你可能想按年份排序,规则可以是年份较早的汽车排在前面。
Comparator
和 Comparable
接口允许你指定用于排序对象的规则。
能够指定排序规则还允许您改变字符串和数字的排序方式。
5.1 Comparator(比较器)
实现了 Comparator
接口的对象被称为比较器。
Comparator
接口允许你创建一个包含 compare()
方法的类,该方法比较两个对象以决定哪个在列表中应排在前面。
compare()
方法应返回一个数字,该数字:
- 为负数时,表示第一个对象应在列表中排在前面。
- 为正数时,表示第二个对象应在列表中排在前面。
- 为零时,表示顺序无关紧要。
实现 Comparator
接口的类可能如下所示:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
// 定义一个 Car 类
class Car {
public String brand; // 品牌
public String model; // 型号
public int year; // 年份
// 构造函数
public Car(String b, String m, int y) {
brand = b;
model = m;
year = y;
}
}
// 创建一个比较器
class SortByYear implements Comparator {
// 实现 compare 方法
public int compare(Object obj1, Object obj2) {
// 确保传入的对象是 Car 类型
Car a = (Car) obj1;
Car b = (Car) obj2;
// 比较两辆车的年份
if (a.year < b.year) return -1; // 第一辆车年份更小
if (a.year > b.year) return 1; // 第一辆车年份更大
return 0; // 两辆车年份相同
}
}
public class Main {
public static void main(String[] args) {
// 创建一个汽车列表
ArrayList<Car> myCars = new ArrayList<Car>();
myCars.add(new Car("BMW", "X5", 1999));
myCars.add(new Car("Honda", "Accord", 2006));
myCars.add(new Car("Ford", "Mustang", 1970));
// 使用比较器对汽车进行排序
Comparator myComparator = new SortByYear();
Collections.sort(myCars, myComparator);
// 显示排序后的汽车
for (Car c : myCars) {
System.out.println(c.brand + " " + c.model + " " + c.year);
}
}
}
使用 Lambda 表达式
为了简化代码,比较器可以被替换为具有与 compare()
方法相同参数和返回值的 Lambda 表达式:
Collections.sort(myCars, (obj1, obj2) -> {
Car a = (Car) obj1;
Car b = (Car) obj2;
if (a.year < b.year) return -1;
if (a.year > b.year) return 1;
return 0;
});
5.2 Comparable 接口
Comparable
接口允许对象通过 compareTo()
方法指定其自身的排序规则。
compareTo()
方法接受一个对象作为参数,并将可比较对象与该参数进行比较,以决定哪个在列表中应排在前面。
与比较器类似,compareTo()
方法返回一个数字,该数字:
- 为负数时,表示可比较对象应在列表中排在前面。
- 为正数时,表示另一个对象应在列表中排在前面。
- 为零时,表示顺序无关紧要。
许多原生 Java 类(如
String
和Integer
)都实现了Comparable
接口。这就是为什么字符串和数字不需要比较器就可以进行排序的原因。
示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
// 定义一个可比较的 Car 类
class Car implements Comparable<Car> {
public String brand; // 品牌
public String model; // 型号
public int year; // 年份
// 构造方法,用于初始化 Car 对象
public Car(String b, String m, int y) {
brand = b;
model = m;
year = y;
}
// 实现 compareTo 方法,决定此对象如何与其他 Car 对象进行比较
@Override
public int compareTo(Car other) {
if (year < other.year) return -1; // 如果此车的年份小于另一辆车的年份,则返回 -1,表示此车较小
if (year > other.year) return 1; // 如果此车的年份大于另一辆车的年份,则返回 1,表示此车较大
return 0; // 如果两车的年份相同,则返回 0,表示两车相等(在年份上)
}
}
public class Main {
public static void main(String[] args) {
// 创建一个汽车列表
ArrayList<Car> myCars = new ArrayList<Car>();
myCars.add(new Car("BMW", "X5", 1999));
myCars.add(new Car("Honda", "Accord", 2006));
myCars.add(new Car("Ford", "Mustang", 1970));
// 对汽车进行排序
Collections.sort(myCars);
// 显示汽车列表
for (Car c : myCars) {
System.out.println(c.brand + " " + c.model + " " + c.year);
}
}
}
5.3 Comparator vs. Comparable
比较器(comparator)是一个具有一个方法的对象,用于比较两个不同的对象。
可比较对象(comparable)是一个可以与其他对象进行比较的对象。
在可能的情况下,使用 Comparable
接口更容易,但 Comparator
接口更强大,因为它允许你对任何类型的对象进行排序,即使你不能更改其代码。
八、文件处理
1. 文件
java.io
包中的File
类允许我们处理文件。
如需使用File
类,请创建该类的对象,并指定文件名或目录名。
示例:
import java.io.File; // 导入 File 类
File myObj = new File("filename.txt"); // 规定文件名
File
类中有许多方法来创建文件并且获取有关文件的信息:
方法 | 类型 | 描述 |
---|---|---|
canRead() |
Boolean | 测试文件是否可读。 |
canWrite() |
Boolean | 测试文件是否可写。 |
createNewFile() |
Boolean | 创建空文件。 |
delete() |
Boolean | 删除文件。 |
exists() |
Boolean | 检查文件是否存在。 |
getName() |
String | 返回文件名。 |
getAbsolutePath() |
String | 返回文件的绝对路径名。 |
length() |
Long | 返回文件的大小,以字节为单位。 |
list() |
String[] | 返回目录中文件的数组。 |
mkdir() |
Boolean | 创建目录。 |
2. 创建/写入文件
2.1 创建文件
createNewFile()
方法。此方法返回一个布尔值:如果文件已成功创建,则为 true
,如果文件已存在,则为 false
。请注意,该方法包含在 try...catch
块中。这是很必要的,因为如果发生错误(如果由于某种原因无法创建文件),则会抛出 IOException
:
import java.io.File; // 导入 File 类
import java.io.IOException; // 导入 IOException 类来处理错误
public class CreateFile {
public static void main(String[] args) {
try {
File myObj = new File("filename.txt"); // 指定文件路径
if (myObj.createNewFile()) {
System.out.println("文件已创建:" + myObj.getName());
} else {
System.out.println("文件已存在。");
}
} catch (IOException e) {
System.out.println("发生错误。");
e.printStackTrace();
}
}
}
// 输出将是:
// 文件已创建:filename.txt
2.2 写入文件
使用 FileWriter
类及其 write()
方法将文本写入文件。请注意,当完成写入文件时,应使用 close()
方法关闭它:
import java.io.FileWriter; // 导入 FileWriter 类
import java.io.IOException; // 导入 IOException 类来处理错误
public class WriteToFile {
public static void main(String[] args) {
try {
FileWriter myWriter = new FileWriter("filename.txt");
myWriter.write("Java 中的文件可能很棘手,但它很有趣!");
myWriter.close();
System.out.println("成功写入文件。");
} catch (IOException e) {
System.out.println("发生错误。");
e.printStackTrace();
}
}
}
// 输出将是:
// 成功写入文件。
3. 读取文件
3.1 读取文件内容
使用 Scanner
类来读取文本文件的内容:
import java.io.File; // 导入 File 类
import java.io.FileNotFoundException; // 导入这个类来处理错误
import java.util.Scanner; // 导入 Scanner 类以读取文本文件
public class ReadFile {
public static void main(String[] args) {
try {
File myObj = new File("filename.txt");
Scanner myReader = new Scanner(myObj);
while (myReader.hasNextLine()) {
String data = myReader.nextLine();
System.out.println(data);
}
myReader.close();
} catch (FileNotFoundException e) {
System.out.println("发生错误。");
e.printStackTrace();
}
}
}
3.2 获取文件信息
如需获取有关文件的更多信息,使用任意 File
方法:
import java.io.File; // 导入 File 类
public class GetFileInfo {
public static void main(String[] args) {
File myObj = new File("filename.txt");
if (myObj.exists()) {
System.out.println("文件名称:" + myObj.getName());
System.out.println("绝对路径:" + myObj.getAbsolutePath());
System.out.println("是否可写:" + myObj.canWrite());
System.out.println("是否可读:" + myObj.canRead());
System.out.println("文件大小:" + myObj.length());
} else {
System.out.println("文件不存在。");
}
}
}
4. 删除文件
在 Java 中删除文件,使用 delete()
方法:
import java.io.File; // 导入 File 类
public class DeleteFile {
public static void main(String[] args) {
File myObj = new File("filename.txt");
if (myObj.delete()) {
System.out.println("已删除文件:" + myObj.getName());
} else {
System.out.println("删除文件失败。");
}
}
}
// 输出将是:
// 已删除文件:filename.txt
删除文件夹:(文件夹必须为空)
import java.io.File;
public class DeleteFolder {
public static void main(String[] args) {
File myObj = new File("C:\\Users\\MyName\\Test");
if (myObj.delete()) {
System.out.println("已删除文件夹:" + myObj.getName());
} else {
System.out.println("删除文件夹失败。");
}
}
}
// 输出将是:
// 已删除文件夹:Test
- 感谢你赐予我前进的力量