Java 基础
快速入门
环境组成
以下表示 JDK
,JRE
和 JVM
包含关系:
text
+----------------------------------------------+
| JDK |
| +-----------------------+ +----------------+ |
| | JRE | | Other Tools | |
| | +-----------+ | | | |
| | | JVM | | | javac, | |
| | +-----------+ | | java, | |
| | | | javadoc, | |
| | lib (rt.jar, etc.) | | jdb | |
| +-----------------------+ +----------------+ |
+----------------------------------------------+
JDK(Java Development Kit)
:是Java
开发者工具包,它包含了JRE
(Java
运行环境)和其他开发工具(如javac
编译器,javadoc
,jdb
调试器等)。JRE(Java Runtime Environment)
:是Java
运行环境,它是JDK
的一部分,负责提供Java
程序运行所需的环境。它包含了JVM
(Java
虚拟机)和核心类库。Java
类库提供了大量预编译的类,包括基础的数据结构和算法,网络编程,文件操作,图形用户界面等,以方便开发者使用。JVM(Java Virtual Machine)
:是Java
虚拟机,它负责字节码的执行。JVM
是平台相关的,能让相同的Java
字节码在不同的操作系统和硬件平台上运行。
重要特征
Java
作为面向对象编程(OOP)
模型的佼佼者,深受全球开发者的青睐。Java
凭借其强类型特性和自带的垃圾回收(GC)
机制,保证了代码的安全性和健壮性。Java
融合了编译性和解释性的优点,提供了灵活且高效的代码执行方式。Java
具有跨平台性,体现了"Write Once, Run Anywhere"
的理念。
运行机制
Java
编译执行命令:
bash
# 将Java源代码进行编译
➜ javac Hello.java
# 查看生成的字节码文件
➜ ll Hello.class
-rw-r--r--@ 1 mystic staff 1117 1 6 16:13 Hello.class
# 启动Java应用程序
➜ java Hello
Hello World
Java
基本执行流程:
text
+----------------------------+
| Source Code | --- MyClass.java 源代码文件
+----------------------------+
|
▼
+----------------------------+
| Java Compiler | --- 命令:javac(由源代码编译至字节码的过程,被称为 "编译阶段")
+----------------------------+
|
▼
+----------------------------+
| Byte Code (.class file) | --- MyClass.class 字节码二进制文件
+----------------------------+
|
▼
+----------------------------+
| JVM (Java Virtual Machine) | --- 命令:java(不同平台的 JVM 会把字节码转换成能在相应平台运行的机器码,被称为 "解释阶段")
+----------------------------+
|
▼
+----------------------------+
| Running Application | --- MyClass 应用程序
+----------------------------+
Java
同时具有编译性和解释性,这使其既能享受到编译型语言的运行效率,又能享受到解释型语言的跨平台特性。
阶段 | 过程 | 语言特性 | 软件/命令 | 输入 | 输出 | 输出类型 |
---|---|---|---|---|---|---|
开发阶段 | 编译 | 编译性 | javac 编译器 |
Java源代码 (*.java 文件) |
字节码 (*.class 文件) |
二进制字节码 |
运行阶段 | 解释执行 | 解释性 | Java虚拟机 (JVM ) |
字节码 (*.class 文件) |
直接执行 | 无(在 JVM 中执行) |
运行阶段 | 即时编译 (JIT ) |
编译性 | Java虚拟机 (JVM ) |
字节码 (*.class 文件,特别是热点代码) |
机器码 | 以机器码方式直接执行在 JVM 中 |
开发细节
-
Java
源文件以".java"
作为其扩展名,这些文件的主要构成部分是类(class)
定义。 -
Java
应用程序的执行入口为main()
方法。它的标准编写形式如下:public static void main(String[] args) {...}
。 -
需注意,
Java
语言对于大小写字母是严格区分的。 -
Java
方法是由一系列语句组成的,每一个语句都应以分号";"
结束。 -
在一个
Java
源文件中,最多只能有一个public
(公共)类,但是其他类型的类(如私有类等)不受数量限制。 -
如果一个源文件中包含了一个
public
类,那么该源文件必须以该公共类的名字命名。
安装 OpenJDK
下面为大家提供一种免费预编译的 OpenJDK 部署方式
JDK 说明
自 2019
年开始,Oracle JDK(Java Development Kit)
的商业用途需要付费。也就是说,如果你想要在生产环境中使用最新的 Oracle JDK
,并想要获得官方提供的长期支持 (LTS,Long Term Support)
以及更新和补丁,你需要购买 Oracle
的订阅。
然而,Oracle
也提供了免费的 JDK
版本,即 Oracle OpenJDK
。这个 JDK
的版本每三个月更新一次,但它不提供长期支持。此外,Oracle
的 Java SE 8
仍然在商业用途下免费,但这只适用于 2019
年 4
月或以前的版本。
对于个人,学术或研究用途,Oracle JDK
仍然是免费的。
请注意,若希望使用免费的 OpenJDK
,并且需要长期的支持,也可以选择其他提供商的 JDK
,例如:AdoptOpenJDK
(更名为 Eclipse Adoptium
),Amazon Corretto
,Azul Zulu
,或者 Red Hat
的 OpenJDK
等。这些版本一般也提供长期的支持,并且完全免费。
Adoptium OpenJDK
下面我们选择使用 Eclipse Adoptium 的产品来下载 OpenJDK
。
版本 | 截图 |
---|---|
最新 LTS 版 |
|
自选平台或版本 |
如何查看真实的下载地址(GIF
动图操作)?
安装部署 Java8
- 在服务器上下载
OpenJDK
:
bash
root@localhost:~# wget https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u392-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u392b08.tar.gz
- 将其放置到
/usr/local
下:
bash
root@localhost:~# tar -xf OpenJDK8U-jdk_x64_linux_hotspot_2023-12-17-03-51.tar.gz -C /usr/local/
- 更改下解包后的目录名:
bash
root@localhost:~# mv /usr/local/jdk8u402-b04 /usr/local/jdk8
- 在
/etc/profile
末尾添加以下内容:
bash
root@localhost:~# cat << EOF >> /etc/profile
export JAVA_HOME=/usr/local/jdk8
export PATH=\$PATH:\$JAVA_HOME/bin
export CLASSPATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar
EOF
- 重新载入环境变量配置:
bash
root@localhost:~# source /etc/profile
- 检查
java
和javac
工具:
bash
# Java 运行环境
root@localhost:~# java -version
openjdk version "1.8.0_402-beta"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_402-beta-202312161212-b04)
OpenJDK 64-Bit Server VM (Temurin)(build 25.402-b04, mixed mode)
# Java 编译器
root@localhost:~# javac -version
javac 1.8.0_402-beta
Hello World
- 编写
HelloWorld.java
代码:
java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
- 编译与运行:
bash
# 使用 javac 编译 Java 源代码文件
root@localhost:~# javac HelloWorld.java
# 编译成为 Java 字节码文件
root@localhost:~# ll HelloWorld.class
-rw-r----- 1 root root 427 Dec 18 18:41 HelloWorld.class
# 运行 HelloWorld.class 字节码文件
root@localhost:~# java HelloWorld
Hello, World!
编程基础
注释
在 Java
中有三种类型的注释:
- 单行注释:
java
// 这是一个单行注释。
- 多行注释或块注释:
java
/*
这是
一个多行
注释。
*/
- 文档注释:这种类型的注释被
javadoc
工具用来从源文件中生成文档。
java
/**
* 这是一个文档化注释(DocComment)。
* @param name 一个代表名字的字符串
* @return String 一个代表问候的字符串
*/
public String sayHello(String name) {
return "Hello, " + name;
}
变量
Java
在定义变量时遵循 "类型在前,名称在后" 的规则,与 C++
、C#
等语言一样。而元年后诞生的新语言,如 Go
、Rust
则正相反,在定义变量时采取了 "名称在前,类型在后" 的规则,它们在设计之初使语法更贴近自然语言的表达习惯。
java
public class Main {
public static void main(String[] args) {
int age = 30; // 整型变量在 Java 中默认占用4个字节
double score = 90.6; // 双精度浮点型变量在 Java 中默认占用8个字节
char gender = '男'; // 字符型变量在 Java 中占用2个字节
String name = "mystic"; // 字符串类型在 Java 中是一个引用类型,它的长度只受限于可用内存
System.out.println("Age: " + age);
System.out.println("Score: " + score);
System.out.println("Gender: " + gender);
System.out.println("Name: " + name);
}
}
加号
在 Java
中,加号主要有两种用途,一种是作为数学加法运算符,一种是作为字符串连接运算符。
当一个数字和一个字符串相加时,数字将会被转换为字符串,然后再进行字符串连接。
java
public class Main {
public static void main(String[] args) {
// 加号作为数学加法运算符
int a = 5;
int b = 3;
int sum = a + b;
System.out.println("The sum is: " + sum); // 输出 "The sum is: 8"
// 加号作为字符串连接运算符
String str1 = "Hello ";
String str2 = "World";
String message = str1 + str2;
System.out.println(message); // 输出 "Hello World"
// 数字和字符串相加
int num = 30;
String str = "岁";
String result = num + str;
System.out.println(result); // 输出 "30岁"
}
}
数据类型
在 Java
中,每种数据都有其明确定义的数据类型。这种数据类型决定了在内存中为这个特定数据分配的具体空间大小(以 byte
为单位)。这不仅确保数据的安全,并且由于每种数据类型所需要的内存大小是固定的,Java
能够更有效地管理和使用内存。
数据类型 | 分类 | 具体 | 说明 | |
---|---|---|---|---|
基本数据类型 | 数值型 | 整型 | byte | 1 字节,取值范围:-128~127 |
基本数据类型 | 数值型 | 整型 | short | 2 字节,取值范围:-32768~32767 |
基本数据类型 | 数值型 | 整型 | int | 4 字节,取值范围:-2147483648~2147483647(Java 的默认整型) |
基本数据类型 | 数值型 | 整型 | long | 8 字节,取值范围:-9223372036854775808~9223372036854775807 |
基本数据类型 | 数值型 | 浮点型 | float | 4 字节,能表示大约 7 位有效数字的浮点数 |
基本数据类型 | 数值型 | 浮点型 | double | 8 字节,能表示大约 16 位有效数字的浮点数(Java 的默认浮点型) |
基本数据类型 | 字符型 | char | 2 字节,用于表示 Unicode 码点在 U+0000 到 U+FFFF 之间的字符 | |
基本数据类型 | 布尔型 | boolean | 布尔类型,只有两个取值:true 或 false | |
引用数据类型 | 类(Class) | String | 用于表示字符序列,非常常用 | |
引用数据类型 | 类(Class) | Integer | 用于表示整数,是 int 类型的包装类 | |
引用数据类型 | 类(Class) | ArrayList | 用于表示动态数组,能够自动调整其大小 | |
引用数据类型 | 类(Class) | Date | 用于表示日期和时间 | |
引用数据类型 | 类(Class) | Etc. | 类(Class)实际上可以定义任何类型的对象,包括系统内置类和用户定义类 | |
引用数据类型 | 接口(Interface) | - | 用来定义行为的类型,包含方法的签名,但不包含方法的实现 | |
引用数据类型 | 数组(Array) | - | 用于存储同一类型多个值的容器 |
类型转换
基本类型转换
自动类型转换(隐式类型)
自动类型转换通常发生在两种兼容类型之间,且目标类型大于源类型时。在这种情况下,数据将自动转换为更宽的类型,不会丢失信息。
java
int numInt = 100;
long numLong = numInt; // 自动类型转换,从 int 转为 long
强制类型转换(显式类型)
如果需要将兼容的类型进行转换,但目标类型小于源类型。这可能导致数据丢失。在这种情况下,需要使用强制类型转换。
java
double numDouble = 100.99;
int numInt = (int) numDouble; // 强制类型转换,从 double 转为 int
字符串的互转
在 Java
实际的项目开发中,String
与基本数据类型之间的转换是十分常见的,下面举例可供我们在代码中灵活使用。
基本数据类型转换为 String
使用 String.valueOf()
方法,这是最直接的方式。
java
int num = 456;
String str = String.valueOf(num);
使用 +
运算符,对于任何数据类型,Java
都会自动将其他数据类型与 String
相加的结果类型定为 String
。
java
int num = 456;
String str = num + "";
String 转换为基本数据类型
每一种基本数据类型(包含包装类型)都有对应的 parseXXX()
方法(将 String
转换为对应的基本数据类型)。
java
String str = "123";
int num = Integer.parseInt(str);
但请注意,parseXXX()
方法在处理不能转换为对应类型的字符串时,会抛出 java.lang.NumberFormatException
异常。
运算符
在 Java
中的常用运算符及其使用方法:
运算符 | 符号 | 说明 |
---|---|---|
算术运算符 | + | 加法 |
算术运算符 | - | 减法 |
算术运算符 | * | 乘法 |
算术运算符 | / | 除法 |
算术运算符 | % | 取余 |
算术运算符 | ++ | 自增 |
算术运算符 | -- | 自减 |
关系运算符 | == | 等于 |
关系运算符 | != | 不等于 |
关系运算符 | > | 大于 |
关系运算符 | < | 小于 |
关系运算符 | >= | 大于等于 |
关系运算符 | <= | 小于等于 |
逻辑运算符 | && | 逻辑与 |
逻辑运算符 | || | 逻辑或 |
逻辑运算符 | ! | 逻辑非 |
赋值运算符 | = | 简单赋值 |
赋值运算符 | += | 加等于 |
赋值运算符 | -= | 减等于 |
赋值运算符 | *= | 乘等于 |
赋值运算符 | /= | 除等于 |
赋值运算符 | %= | 取余等于 |
三元运算符 | Boolean ? value1 : value2 | 如果 Boolean 为 true,结果为 value1,否则为 value2 |
在 Java
中运算符优先级的顺序(由高到低):
java
[] . () // 数组下标、点操作符、括号
! ~ ++ -- // 逻辑非、位非、自增、自减
* / % // 乘、除、取模
+ - // 加、减
<< >> >>> // 左位移、右位移、无符号右位移
< <= > >= instanceof // 小于、小于等于、大于、大于等于、检查是否为某类型
== != // 等于、不等于
& // 逻辑与、按位与
^ // 逻辑异或、按位异或
| // 逻辑或、按位或
&& // 短路与
|| // 短路或
?= // 赋值、加等、减等、乘等、除等、取模等等
关键字
Java
中的关键字是一些预先定义的、具有特殊意义的单词,它们作为 Java
语言的基本构建块在 Java
代码中起着重要的作用。
以下是 Java
关键字的列表:
text
abstract, assert, boolean, break, byte, case, catch, char, class, const, continue,
default, do, double, else, enum, extends, final, finally, float, for,
goto, if, implements, import, instanceof, int, interface, long, native,
new, package, private, protected, public, return, short, static,
strictfp, super, switch, synchronized, this, throw, throws, transient,
try, void, volatile, while
需注意的是 const
和 goto
是保留关键字,但在 Java
中并未被使用。
另外,true
, false
, null
被称为字面量 (Literals)
,在某些情况下,它们也可以被视为关键字。
用户输入
在 Java
中,我们通常会使用 java.util.Scanner
类来获取用户输入。以下是一个示例:
java
import java.util.Scanner; // 导入 Scanner 类
public class UserInput {
public static void main(String[] args) {
Scanner myObj = new Scanner(System.in); // 创建一个 Scanner 对象
System.out.println("Enter username");
String userName = myObj.nextLine(); // 读取用户输入
System.out.println("Username is: " + userName); // 输出用户输入
}
}
控制结构
顺序控制
在 Java
中,变量通常应在使用之前进行声明和初始化。Java
中定义成员变量时采用合法的前向引用。如:
java
public class ForwardReference {
public static void main(String[] args) {
System.out.println(value); // 错误的前向引用,因为在打印它的值之前它尚未初始化
int value = 10;
}
}
分支控制
在 Java
中,有三种主要的分支控制结构:if
语句、switch
语句和 ?:
(三元运算符)。
java
public class Main {
public static void main(String[] args) {
// if 语句
int num = 10;
if (num > 0) {
System.out.println("Number is positive.");
} else if (num < 0) {
System.out.println("Number is negative.");
} else {
System.out.println("Number is zero.");
}
// switch 语句
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
// 其他 days...
default:
System.out.println("Invalid day");
}
// 三元运算符
String result = (num > 0) ? "Positive" : "Not positive";
System.out.println(result);
}
}
循环控制
在 Java
中,有三种主要的循环结构:for
循环,while
循环和 do...while
循环。
java
public class Main {
public static void main(String[] args) {
// for 循环
System.out.println("FOR LOOP:");
for (int i = 0; i < 5; i++) {
System.out.println("The value of i is: " + i);
}
// while 循环
System.out.println("\nWHILE LOOP:");
int j = 0;
while (j < 5) {
System.out.println("The value of j is: " + j);
j++;
}
// do...while 循环
System.out.println("\nDO...WHILE LOOP:");
int k = 0;
do {
System.out.println("The value of k is: " + k);
k++;
} while (k < 5);
}
}
在 Java
中,break
和 continue
这两个重要的关键字,它们具有控制程序执行流程的能力。
java
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// continue 用于在循环中直接开始下一次循环,忽略剩下的循环代码。
if (j == 2) {
continue;
}
// break 语句通常用在循环或者 switch 语句中,用来立即结束当前的循环或者 switch,跳到下一条可执行代码。
if (i == 3) {
break;
}
System.out.println("The value of i is: " + i);
}
}
}
数组
Java 的数组是一个容器对象,它能够存储固定数量的具有相同类型的值。也就是说,数组的大小一旦创建,就不能改变。每一个存储在数组中的值可以通过索引进行访问。
声明数组
格式:
java
dataType[] arrayName; // 推荐使用
dataType arrayName[]; // 也可以,但不常见
示例:
java
int[] myArray;
初始化数组
创建新数组:
java
myArray = new int[5]; // 它有5个元素,默认值是0
声明并初始化
在声明时直接初始化数组:
java
int[] myArray = new int[5];
也可以指定数组的具体内容:
java
int[] myArray = {1, 2, 3, 4, 5};
访问数组
可以通过数组的索引来访问数组的元素:
java
int firstElement = myArray[0];
也可以修改数组中的元素:
java
myArray[0] = 60;
获取数组长度
通过 length
属性,可以得到数组的长度:
java
int length = myArray.length;
遍历数组
可以使用 for
循环或 foreach
循环来遍历数组:
java
// 使用普通 for 循环遍历数组
for(int i = 0; i < myArray.length; i++) {
System.out.println(myArray[i]);
}
// 使用 foreach 循环遍历数组
for(int num : myArray) {
System.out.println(num);
}
引用传递
在 Java
中,数组是对象,所以在对数组赋值或复制时,是按引用传递的,而不是按值传递。
java
// 当你把一个数组标识符赋值给另一个数组标识符时,你只是复制了数组的引用,而不是数组本身。这意味着这两个引用指向的是同一个数组。
int[] array1 = {1, 2, 3, 4, 5};
int[] array2 = array1;
array2[0] = 10;
System.out.println(array1[0]); // 输出 10
拷贝数组
如果想要在 Java
中得到数组的一个新副本,需要创建一个新的数组并复制每个元素,可以使用 System.arraycopy
或 Arrays.copyOf
方法。
java
public class Main {
public static void main(String[] args) {
int[] array1 = {1, 2, 3, 4, 5};
int[] array2 = new int[array1.length];
// 1. 使用 System.arraycopy 方法
System.arraycopy(array1, 0, array2, 0, array1.length);
// 2. 使用 Arrays.copyOf 方法
// int[] array2 = Arrays.copyOf(array1, array1.length);
array2[0] = 10;
System.out.println(array1[0]); // 输出 1
System.out.println(array2[0]); // 输出 10
}
}
反转数组
java
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
System.out.println("原始数组:" + Arrays.toString(array));
int temp = 0;
int len = array.length;
for (int i = 0; i < len / 2; i++) {
temp = array[len - 1 - i];
array[len - 1 - i] = array[i];
array[i] = temp;
}
System.out.println("反转数组:" + Arrays.toString(array));
}
}
二维数组
在 Java
中,二维数组其实就是数组的数组,通常可以用来表示一个表格或者矩阵。
初始化二维数组
java
int[][] array = new int[3][3]; // 3x3的二维数组,所有元素初始化为0
在声明的同时给二维数组赋值:
java
int[][] array = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
这个数组可以被看成一个 3x3 的矩阵。
访问二维数组元素
可以通过两个索引来访问二维数组的一个元素:
java
int value = array[2][2]; // 此时, value 的值是9
遍历二维数组
可以使用两层嵌套的 for
循环来遍历二维数组:
java
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.println(array[i][j]);
}
}