Core Java I - Java 基础
Java 术语
缩写 | 英文 | 解释 |
---|---|---|
JDK | Java Development Kit | 开发 Java 程序需要使用的软件,包含 JRE 和开发工具 |
JRE | Java Runtime Environment | 用来开发程序。用户运行 Java 程序需要使用的软件,包含 JVM 和核心类库 |
JVM | Java Virtual Machine | Java 虚拟机,是 Java 程序的运行环境,用来实现跨平台 |
Java SE | Standard Edition | 用于桌面或简单服务器应用的 Java 平台 |
Java EE | Enterprise Edition | 用于复杂服务器应用的 Java 平台 |
Java ME | Micro Edition | 用于手机和 他小型设备的 Java 平台 |
Java 数据类型
Java 中的数据类型可分为 基本数据类型
和 引用数据类型
基本数据类型:char byte shrot int long float double boolean
引用数据类型:对象、数组…
调用方式
Java 总是call by value
的,即按值调用(call by ...
指按…调用)。
所有方法得到的形参都是实参的一个拷贝 —— 这导致任何方法都不能修改外部变量的引用 。
基本数据类型
Java 是一种强类型语言,在 Java 中,有 8 种基本类型(primitive type)。
四种整型
类型 | 储存 | 范围 |
---|---|---|
byte | 1 字节 | -128 ~ 127 |
short | 2 字节 | -32768 ~ 32767 |
int | 4 字节 | -2147483648 ~2147483647 |
long | 8 字节 | -9223372036854775808 ~ 9223372036854775807 |
与 C/C++ 不同,Java 整型的范围与宿主环境无关
整数字面量默认是int
类型,使用long
类型需要加L
后缀。
为了增强数字的易读性,我们还可以为数字字面量加下划线:
例如1_000_000
、1_000_______000
和1000000
均表示 100w。
另外,二进制使用0b
/0B
前缀,八进制使用0
前缀,十六进制使用0x
/0X
前缀。
两种浮点型
类型 | 储存 | 范围 |
---|---|---|
float | 4 字节 | ± 3.40282347E+38F |
double | 8 字节 | ± 1.79769313486231570E+308 |
浮点数字面量默认是double
类型,使用float
类型需要加f
/F
后缀。(double
类型也有后缀d
/D
)
Java 也允许使用科学计数法,例如103E5
表示10300000
。还有十六进制的科学计数法0x103p5
。
在 Java 中,浮点计算溢出(1d/0)或出错(0d/0)时的三个特殊浮点值:
Double.POSITIVE_INFINITY
(正无穷,定义为 1d/0)
Double.NEGATIVE_INFINITY
(负无穷,定义为 -1d/0)
Double.NaN
(not a number,定义为 0d/0)
若想要验证某个值是否为 infinite 的,或是否不是一个数字,应使用 Double 包装类中的方法:
boolean isNaN(double v);
boolean isFinite(double d);
boolean isInfinite(double v);
关于isNaN
这个方法:
public static boolean isNaN(double v) {
return (v != v);
// Java 认为任意两个 not a number 都是不相等的
}
关于浮点数的运算,有些计算机采用 80 位的浮点寄存器,运算时会临时将计算结果存放在 80 位浮点寄存器中,然后截断两个字节至 64 位 —— 这样有更好的计算精度。但对于某些不采用这种运算方法的机器来说,运算结果就会有些许差别。
Java 追求可移植性,即在不同机器上,运算结果都应该相同,但上述这一点明显违背可移植性。在很早的标准中,任何浮点数运算的中间过程都应被截断,以保证可移植性,但遭到了很多反对。在各方撕逼下,Java 给出了折中的解决方法。
默认情况下允许使用更精确地计算方法,但使用strictfp
关键字修饰的变量,在运算时会严格进行截断。
字符类型
类型 | 储存 |
---|---|
char | 2 字节 |
Unicode 与 UTF-16
Java 中的 char 类型通过 UTF-16 的约定来描述一个代码单元,即 Java 的 char 使用 UTF-16 的编码方式编码 Unicode 字符。
Unicode 是一种标准和协定,是一个字符集,他描述了其包含的每个字符与这个字符所对应的一个唯一数值的对应关系,就像一个hashmap<int, char>
,每个字符都有一个唯一的 key。
而 UTF-16 是一种针对 Unicode 这种标准的编码方式(或 Unicode 的一种实现),它描述了从 Unicode 字符映射到某个代码的关系。
关于编码,有两个术语:
代码点(Code Point):Unicode 的实现中,每一个字符对应的唯一代码称为代码点。
代码单元(Code Unit):Unicode 的实现中,每一个代码点可由一个或多个代码单元表示,例如,UTF-8 通过一个字节来描述一个代码单元,而 UTF-16 则使用两个字节。
UTF-16 就是字符到代码点的关系集合,是一种可变长的非常灵活的编码方式。
为什么我们不直接使用 Unicode 标准而是对 Unicode 进行再编码呢?
我们刚才将 Unicode 标准比喻为一个 hashmap<int, char>
,试想一下,如果这个字符集的字符数量超过了 65535 个,那么我们就要将 int 换成 long(我们使用 C/C++ 来描述问题),防止溢出。
这显然对 ASCII 是不公平的,本来人家只需要一个字节就能存下,现在却被迫用两个甚至四个字节,这大大浪费了带宽和存储空间。
于是 UTF-8 出现了,它通过非常巧妙的编码方式实现了"针对不同的 Unicode 字符使用不同数量的代码单元来描述"这件事情,例如 ‘A’ 在 UTF-8 中使用一个代码单元(即一个字节)描述,而某个汉字则使用两个到三个代码单元描述。
另外,这篇文章不错,比我解释的要更清晰和详细,还给出了 UTF-8 和 UTF-16 的具体编码方式。
布尔类型
类型 | 储存 | 范围 |
---|---|---|
boolean | 1 字节 | true or false |
与 C/C++ 不同,在 Java 中,不存在任何从非布尔类型转换为布尔类型的隐式转换。
数组
数组初始化
声明一个数组:int[] arr;
(也可以使用这种风格int arr[];
)。
int[] arr;
int arr[];
初始化数组:
布尔数组将会初始化为false
,数值数组初始化为0
,对象数组则为null
。
new int[10];
new int[]{1, 2, 3, 4};
申请一段内存空间,将arr
指向该内存:
int[] arr = new int[10];
声明数组及初始化的简写形式:
int[] arr = {1, 2, 3, 4};
数组拷贝
Arrays 中的静态方法:
static int[] copyOf(int[] original, int newLength);
例如:
int[] arr2 = Arrays.copyOf(arr1, arr1.length);
变量
变量名
Java 允许使用很多 Unicode 字符作为变量名,若想要知道哪些字符可用作命名,可以通过 Character
类的 isJavaldentifierStart
和 isJavaldentifierPart
方法来检测。
!注意,$
作为变量名合法,但不要这样做,因为 Java 生成内部类字节码时会将$
作为文件名的一部分,可能会导致一些很麻烦的问题。
声明
在 C/C++ 中,extern int a;
被称为声明,int a = 10;
被称为定义。
而在 Java 中,不区别声明和定义,未初始化的变量不允许使用,是编译期错误。
常量
通过final
指示常量,例如final int a = 10;
。常量只能被赋值一次,此后便无法更改。
除此之外,final
还可以修饰方法和类,表示"最终的",不可重写或继承。
final class className {
// ...
final public void method() {
// ...
}
}
!!!注意,final
与 C++ 中的const
不同,它关注的是变量直接保存的数据。对于引用类型来说(他就是一个指针),final
禁止地址的变化,但不禁止更改该地址处的数据。
引用数据类型
类和对象,将在面向对象中讨论
Java 类型转换
隐式转换
二元操作
进行运算时会将两个操作数转换为同一类型,遵循向精度更高的方向转化。
例如:
int + double → double + double
int + long → long + long
int | long | float | double |
---|---|---|---|
→ → | → → | → → | → → |
if 存在 double
则另一个转换为 double
else if 存在 float
则另一个转换为 float
else if 存在 long
则另一个转换为 long
else
两操作数一起转换为 int
在其他情况中,隐式转换发生的情况很少,Java 比 C/C++ 严格的多,例如 long 不会隐式转换为 int。
显式转换
与 C/C++ 风格的类型转换完全相同。
Java IO
读取输入
Scanner
Scanner 对象用于读取用户输入,或读取文件。
构造:
Scanner(InputStream source)
输入数据:
String next();
// 读入下一个字符串
int nextInt();
// 读入下一个整行
// ... nextDouble, nextFloat, nextBoolean...
判断是否存在下一个数据:
boolean hasNext();
// 下一个读入的数据是否可以认为是字符串
boolean hasNextInt();
// 下一个读入的数据是否可以认为是 int
// ... hasNextDouble, hasNextFloat, hasNextBoolean...
Console
Console 对象可以用于读取不回显的数据,例如密码。
该类仅存在单一实例,由System.console()
方法返回。
Console console = System.console();
常用方法:
char[] readPassword([String fmt, ... args]);
// 读取用户输入,以回车结束,不回显
char[] readPassword();
// 提供一个可格式化的字符串提示
格式化输出
关于输出,我们通常调用方法操作System.out
对象,这个对象实际上是PrintWriter
类的实例,PrintWriter
还可用于文件写入。
C 风格的格式化输出:
PrintWriter printf(String format, Object... args);
常用输出方法:
void println(type param);
void print(type param);
格式化字符串:
static String format(String format, Object... args);
// 将字符串格式化后返回而不输出
!注意,%s
可以格式化任意对象,其实现中将调用Formattable
接口的formatTo()
方法,否则将调用toString()
方法。
文件 IO
Scanner
构造一个扫描文件的 Scanner:
Scanner(Path source [, String charsetName]);
Scanner(File source [, String charsetName]);
Path(Path 是一个接口类型) 和 File 对象可以分别由Paths.get()
方法和File
的构造方法返回。
关于路径,若我们想使用相对路径,那么首先就要知道目前的工作路径,这个路径位于启动 Java 程序的环境的工作目录下。
我们也可以通过String dir = System.getProperty("user.dir");
得到这个路径,且路径末尾不带\
。
PrintWriter
前面提到的System.out
是PrintWriter
的实例,针对文件读写,我们可以直接构造一个PrintWriter
对象。
PrintWriter(String fileName, String csn);
// 指定文件名与字符集
PrintWriter(File file, String csn);
// 指定文件对象与字符集
然后就可以使用常用方法对文件进行输出。
!注意,PrintWirter 对象构造后,目标文件将被重写,若想向文件追加内容,应使用FileWriter
。
FileWriter
FileWriter((File file | String filename) [, boolean append]);
// 第二个可选参数决定了追加 or 重写
该类实现了Writer
接口:
void write(String str);
Writer append(CharSequence csq);
Java 流程控制
Java 的块作用域
与 C++ 不同, Java 不允许在块内重定义块外的变量。
下面的 Java 代码是无法通过编译的:
//...
int var;
{
int var;
//...
}
//...
条件、循环
与 C/C++ 完全相同。
for each 循环
对于数组或某个实现了Iterable
接口的集合,我们可以使用 for each 循环来遍历该集合。
for (Type e : collention){
//...
}
switch 语句
与 C/C++ 的 switch 完全相同。
不过,Java 提供了带 lambda 表达式的 switch 语句,例如:
switch(/* expression */) {
case /* constant */ -> {
//...
}
case /* constant */-> {
//...
}
default -> {
//...
}
}
带标签的 break
goto 语句虽然多遭诟病,但其在跳出深层循环时却意外地简洁。
Java 当然不会引入 goto 语句,但它提供了带标签的 break —— 作为 goto 跳出深层循环的替代方案。
用法大概是这样的:
loop1:
while(/* condition */) {
loop2:
for(/* expression */) {
loop3:
for(/* expression */) {
//...
if(/* condition */) {
break loop2;
// 将会跳出第二层循环
}
//...
}
}
}
另外,带标签的 break 可以实现 goto 的部分功能。
例如下面的代码是合法的:
//...
lable:
{
//...
if(/* condition */) {
break lable;
}
}
//...
可以跳出到外层块,但不允许跳入内层块。
Java 命令行参数
main 方法的字符串数组参数将接收来自命令行的参数。
java {class} {params}
字符串数组中的值将来自上述的{params}
,且以空格作为分隔符。
Java 程序运行
Java 项目结构
Project → Module → Package → Class
项目 模块 包 类
Package 用来对类进行分类(关于包的详细介绍将在后面展开)。
定义:
表明当前类位于哪个包里
package {packageName};
导入:
import {packageName}
在 Java 中,每个.java
文件中有且仅有一个public
类,这个类就是该类对外提供的唯一接口。
Java 通过某个类的主方法main
开始运行程序。
编译
javac *.java
java MainClass
*.java
是源代码,经过 javac.exe
编译成 java 字节码文件,然后通过java.exe
运行。
javac.exe
是编译器。java.exe
是解释器。
jshell
在 shell 中输入jshell
运行jshell
。
jshell
是与 Java 的即时交互环境。
内存
Java 的内存分为五个区域:
栈、堆、方法区、本地方法栈、寄存区。
调用方法时,会将方法区中的方法 copy 一份到栈中执行。
Java IDE - intelliJ IDEA
常用快捷键
键 | 说明 |
---|---|
Ctrl + Alt + Space | 唤出代码补全 |
Ctrl + Shift + 方向 | 移动整行 |
Ctrl + D | 复制整行 |
Ctrl + Y | 删除行 |
Ctrl + W | 扩展选取 |
Ctrl + Shift + W | 取消扩展选取 |
Ctrl + Alt + L | 格式化代码 |
Shift + Alt | 多选 |
鼠标中键 | 列选择 |
Shift + Alt + Insert | 切换行选择和列选择 |
Shift + F6 | 重构 |
Alt + Enter | 导入包,自动修正代码 |
Ctrl + Alt + M | 抽取方法 |