Basic Java⚓︎
约 4619 个字 214 行代码 预计阅读时间 26 分钟
Elementary Programming⚓︎
Identifiers⚓︎
标识符(identifiers) 是一个包括字母、数字、下划线 (underscores)(_)和美元符号($)的字符序列。
- 不能以数字开头
- 不能是保留字,
true、false和null也不行 - 可以是任意长度
- 区分大小写
Variables⚓︎
变量的声明、赋值和初始化语法和 C/C++ 基本一样。但不同之处在于
- Java 不区分变量的声明和定义
- Java 不会对方法内的局部变量赋予一个默认值,而使用未初始化的变量会发生编译错误
final⚓︎
final 的三种用法:
-
修饰变量:
-
final变量是不可改变的,但它的值可以在运行时初始化,也可以在编译时初始化,甚至可以放在构造函数中初始化,而不必在声明的时候初始化,所以下面的语句均合法: -
如果修饰引用变量,则表示这个变量不可再指向其它对象,而不是表示对象内容本身不可更改
-
-
修饰方法:表示一个函数不可更改,也就是不能被重载了,而不是修饰返回值的
- 修饰类:表示整个类不能被继承了(自然里面的所有方法也相当于被加了
final)
注:Java 有保留字
const,但是没有任何实际功能。
Numerical Data Types⚓︎
数值基本类型:
| 类型 | 大小 | 最小值 | 最大值 |
|---|---|---|---|
byte |
1B | -128 | 127 |
short |
2B | -32768 | 32767 |
int |
4B | –2,147,483,648 | 2,147,483,647 |
long |
8B | –9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
float |
4B | 大约 –3.4E+38,有 7 个有效位 | 大约 3.4E+38,有 7 个有效位 |
double |
8B | 大约 –1.7E+308,有 15 个有效位 | 约 1.7E+308,有 15 个有效位 |
Java 中整型的范围与运行 Java 代码的机器无关,解决了平台移植的问题。
数值运算符和 C/C++ 基本一致,故不展开介绍。不过这里提一下需要注意的点:
- 在 Java 中,当
byte、short或char类型参与算术运算时,它们会被自动提升为int类型进行计算
特殊的数字格式:
Literals⚓︎
- 整数字面量
- 只能赋给整数变量
- 当字面量太大,变量无法容纳时将会发生编译错误
- 默认为
int类型;若要指定为long类型,需要在附加一个L或l后缀
- 浮点数字面量
- 默认为
double类型 - 若要指定为
float类型,需要在附加一个F或f后缀;或者附加一个D或d后缀来显式指定double - 特殊的浮点数值:
- 正无穷大:
Double.POSITIVE_INFINITY - 负无穷大:
Double.NEGATIVE_INFINITY - NaN(不是一个数字
) :Double.NaN- 用
Double.isNaN来判断是否是数字
- 用
- 正无穷大:
- 默认为
Type Casting⚓︎
实线表示无信息丢失的转换,虚线表示可能有精度损失的转换。
类型转换(type casting) 分为:
- 隐式(implicit) 转换:
- 类型拓宽:
double d = 3
- 类型拓宽:
- 显式(explicit) 转换:
- 类型缩窄:
int i = (int)3.0; - 小数部分截断:
int i = (int)3.9;
- 类型缩窄:
如果出现类型缩窄但没有显式转换的情况,编译器很可能会报错。
四舍五入:
Selections⚓︎
布尔类型:
- 关键字为
boolean,取值只有true和false - 整型与布尔型不能直接转换
- 所以当
even为boolean变量时,if (even != 0)是错的
- 所以当
if-else 语句:
- 基本上和 C/C++ 语法一致
-
else自己匹配相同块内最近的if语句
switch-case 语句:
- 基本上和 C/C++ 语法一致(同样注意各
case结束后break的使用! ) switch语句支持以下类型:- 基本整数类型:
byte、short、char、int - 包装类:
Byte、Short、Character、Integer - 枚举:
enum - 字符串:
String - 引用类型:自 Java 17/21 起支持所有
Object(通过模式匹配) - 空值:自 Java 17/21 起支持
null(需显式写出case null)
- 基本整数类型:
case语句的值的类型必须和switch语句的值相同,且仅支持常量表达式(不能在表达式中包含变量)
条件运算符:boolean-expression ? exp1 : exp2
运算符优先级:
如果具有相同优先级的运算符彼此相邻,它们的结合性决定了计算顺序:除了赋值运算符(它是右结合的 (right-associative))之外,所有二元运算符都是左结合的 (left-associative)。
Mathematical Functions, Characters, and Strings⚓︎
java.lang.Math⚓︎
- 类常量:
PI、E -
类方法:
-
三角函数:
-
指数函数:
-
舍入函数:
-
max(a, b)和min(a, b) abs(a)- 随机函数:
random(),返回[0.0, 1.0)范围内的随机double值
-
Character Data Type⚓︎
- Java 字符使用 Unicode 字符集和 UTF-16 编码方案,占用 2 个字节,以
\u开头,表示为四个十六进制数字,从\u0000到\uFFFF - 一定用单引号包裹
- 递增运算符
++和递减运算符--也可以用于char变量,以获取下一个或前一个 Unicode 字符 -
字符和整数类型间的转换:
-
Character类方法:
String Type⚓︎
String实际上是 Java 库中的一个预定义类,被称为引用类型(reference type),而不是基本类型 (primitive type)-
初始化:
-
String对象的简单方法(实例方法(instance methods)) :length() // 字符串长度 charAt(index) // 指定索引下的字符 concat(s1) // 和另一个字符串拼接 toUpperCase() // 所有字母转大写 toLowerCase() // 所有字母转小写 trim() // 移除字符串两边的空白字符- 调用实例方法的语法:
referenceVariable.methodName(arguments) - 非实例方法被称为静态方法(static methods),可以在不使用对象的情况下调用
- 调用实例方法的语法:
-
比较字符串:
equals(s1) // 两个字符串是否相等 equalsIgnoreCase(s1) // 同 equals,但大小写不敏感 compareTo(s1) // 字符串是否大于 s1,返回一个整数, // 正数/0/负数分别表示比 s1 大/相等/小 compareToIgnoreCase(s1) // 同 compareToIgnoreCase,但大小写不敏感 startswith(prefix) // 字符串是否以特定前缀开始 endsWith(suffix) // 字符串是否以特定后缀结束- 注意:使用
==是在比较两个引用是否指向同一个对象(一般不会这么做,往往是搞错用法了)
- 注意:使用
-
获取子字符串:
-
在字符串中寻找字符或子字符串
indexOf(ch) // 字符 ch 第一次出现时的索引,若没找到返回 -1 indexOf(ch, fromIndex) // 从 fromIndex 开始,字符 ch 第一次出现时的索引,若没找到返回 -1 indexOf(s) // 字符串 s 第一次出现时的索引,若没找到返回 -1 indexOf(s, fromIndex) // 从 fromIndex 开始,字符串 s 第一次出现时的索引,若没找到返回 -1 // 寻找最后一次出现的索引,和前 4 个方法对应 lastIndexOf(ch) lastIndexOf(ch, fromIndex) lastIndexOf(s) lastIndexOf(s, fromIndex) -
字符串与数字的转换:
// 字符串 —> 数字 int intValue = Integer.parseInt(intString); double doubleValue = Double.parseDouble(doubleString); // 数字 -> 字符串 String s1 = String.valueOf(number1); String s2 = number2 + "";valueOf是String类的静态方法- 实际上
valueOf做的事是直接调用并返回该对象的toString()方法(除了null对象返回字符串"null") ,后者是由Object定义的- 对于自定义类,我们可以重写
toString()方法,返回一个包含人类可读的字符串,并能合理描述类的对象的String对象
- 对于自定义类,我们可以重写
-
替换和分割字符串:
// 返回一个新的字符串,将此字符串中所有匹配的字符替换为新字符 replace(oldChar: char, newChar: char): String // 返回一个新的字符串,该字符串将此字符串中的第一个匹配子串替换为新子串 replaceFirst(oldString: String, newString: String): String // 返回一个新的字符串,该字符串将此字符串中所有匹配的子字符串替换为新子字符串 replaceAll(oldString: String, newString: String): String // 返回由 delimiter(分隔符)分割的子字符串组成的字符串数组 split(delimiter: String): String[]- 支持正则表达式(regular expressions)(包括
matches方法)
- 支持正则表达式(regular expressions)(包括
-
字符串格式(静态方法
) :String.format(format, item1, item2, ..., itemk) -
+运算(拼接) :- 字符串和数字相加时,数字会自动转换为字符串
-
块 (block) 语法(和 Python 类似
) :- 起止各一行
""",单独占一行(开头"""后不能直接跟内容) - 内容自动去掉公共前缀空白,保留相对缩进
- 起止各一行
-
String是不可变的(immutable),即创建String实例后内容无法再更改- 因此没有任何函数能改变字符串的内容
- 但声明为
String引用的变量可以在任何时候改变以指向其他String对象
为什么要“不可变”
来自 wk 课件(
但看起来像是 AI 回答的)- 并发 / 线程安全
- 无状态:对象只读,天然支持多线程共享,无需同步
- 无竞争:写并发代码时不用担心“读到一半被修改”
- 哈希与索引
- hashCode 缓存:计算一次后缓存到字段
hash,后续HashMap/get直接复用,复杂度从 O(n) -> O(1) - 键可信:作为
HashMap/HashSet的 key 时,中途内容变化导致哈希漂移的灾难不可能发生(对比char[]或StringBuilder)
- hashCode 缓存:计算一次后缓存到字段
- 字符串常量池(StringTable)
intern()复用:字面量自动入池,相同内容全局一份,节省堆内存- 地址比较:
"foo"=="foo"直接返回true,JVM 级别优化
- 安全性与完整性
- 类加载器隔离:类名、文件路径、权限字符串一旦传入就无法被篡改,防止“在 check 之后、use 之前”被恶意代码改掉
- 网络 / 文件句柄:
new URL("http://xxx")的协议、主机名不可变,避免校验后地址被替换
- 编译器 & JVM 优化
- 字符串折叠:编译期常量表达式
"a" + "b"直接变成"ab",减少运行时拼接 - 栈上优化:逃逸分析后不可变对象可拆成标量替换,消灭堆分配
- 共享子串:JDK 7 以前 substring 共享底层
char[](offset + count) ,避免复制;JDK 7 之后虽然改为复制,但仍保留不可变语义,让 JIT 放心做循环不变量外提等优化
- 字符串折叠:编译期常量表达式
- 要想支持字符串的修改,可以采用
StringBuilder和StringBuffer类(之后会介绍)
更多
String的 API 见官方文档。
Input: The Scanner Class⚓︎
使用步骤:
- 导入包:
import java.util.Scanner; - 创建对象:连接到输入流(如
System.in) - 读取数据:调用相应的方法获取输入
- 关闭资源:使用完毕后关闭流(良好的编程习惯)
例子
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
// 1. 创建 Scanner 对象,读取标准输入
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
// 2. 读取数据
if (sc.hasNextInt()) {
int num = sc.nextInt();
System.out.println("你输入的是:" + num);
}
// 3. 关闭资源
sc.close();
}
}
常用读取方法:
Scanner(source: File) // 创建一个从指定文件读取数据的 Scanner 对象
Scanner(source: String) // 创建一个从指定文件读取数据的 String 对象
close()
hasNext(): boolean // 如果 Scanner 的输入中还有另一个 token,则返回 true
next(): String
nextByte(): byte
nextShort(): short
nextInt(): int
nextLong(): long
nextFloat(): float
nextDouble(): double
useDelimiter(pattern: String): Scanner // 设置分隔符模式
-
next():读取下一个标记 (token),以空白符(空格、制表符和换行符等)作为分隔- 可简单理解为读取一个单词,跳过开头的空白字符,并且再次读到空白字符就立即停止读取(空白字符不会读进来)
-
nextLine():读取整行内容,直到遇到回车符 nextInt():将下一个标记解析为int类型nextDouble():将下一个标记解析为double类型hasNext...():判断是否还有下一个指定类型的内容,常用于校验输入
也可以设置字符编码
注意事项
nextInt()与nextLine()混用问题:- 当先调用
nextInt()再调用nextLine()时,nextInt()只会读取数字,而数字后面的回车换行符会留在缓冲区;接着调用的nextLine()会直接读取到这个回车符,导致看起来好像“跳过”了输入 - 解决方法:在
nextInt()之后多写一行额外的nextLine()来消费掉那个多余的回车符
- 当先调用
- 性能问题:内部使用正则表达式进行匹配,虽然功能强大且方便,但读取速度较慢
- 如果对性能需求敏感,建议改用
BufferedReader以获得更好的性能
- 如果对性能需求敏感,建议改用
Formatted Output⚓︎
格式化输出语句:System.out.printf(format, items)。其中 format 是一个包含字符串和格式说明符的字符串。格式说明符(format specifier) 指定 item(可能是一个数值、字符、布尔值或字符串)应该如何显示,且都以百分号开始。
常用说明符:
%b:布尔值%c:字符%d:整数%f:浮点数%e:采用标准科学计数法的数字%s:字符串
占位符完整格式为:%[index$][标识]*[最小宽度][.精度]转换符。
%:占位符的起始字符,若要在占位符内部使用%,则需要写成%%[index$]:位置索引从 1 开始计算,用于指定对索引相应的实参进行格式化并替换掉该占位符[标识]:用于增强格式化能力,可同时使用多个[标识],但某些标识是不能同时使用的[最小宽度]:用于设置格式化后的字符串最小长度,若使用[最小宽度]而无设置[标识],那么当字符串长度小于最小宽度时,则以左边补空格的方式凑够最小宽度[.精度]:对于浮点数类型格式化使用,设置保留小数点后多少位转换符:用于指定格式化的样式,和限制对应入参的数据类型
字符、字符串的格式化
- 占位符格式:
%[index$][标识][最小宽度][转换符] - 可用标识:
-:在最小宽度内左对齐,右边用空格补上
- 可用转换符:
s:字符串类型c:字符类型,实参必须为char或int、short等可转换为char类型的数据类型,否则抛IllegalFormatConversionException异常b:布尔类型,只要实参为非false的布尔类型,均格式化为字符串true,否则为字符串falsen:平台独立的换行符(与通过System.getProperty("line.separator")是一样的)
整数的格式化
- 占位符格式:
%[index$][标识]*[最小宽度]转换符 -
可用标识:
-:在最小宽度内左对齐,不可以与0标识一起使用0:若内容长度不足最小宽度,则在左边用0来填充#:对 8 进制和 16 进制,8 进制前添加一个0,16 进制前添加0x+:结果总包含一个+或-号- 空格:正数前加空格,负数前加
-号 ,:只用于十进制,每 3 位数字间用,分隔(:若结果为负数,则用括号括住,且不显示符号
-
可用转换符:
b:布尔类型d:整数类型(十进制)x:整数类型(十六进制)o:整数类型(八进制)n:平台独立的换行符,也可通过System.getProperty("line.separator")获取
浮点数的格式化
- 占位符格式:
%[index$][标识][最小宽度][.精度]转换符 - 可用标识:同整数
- 可用转换符:
b:布尔类型n:换行符f:浮点数型(十进制) ,显示 9 位有效数字,且会进行四舍五入a:浮点数型(十六进制)e:指数类型(如 9.38e+5)g:浮点数型没,比%f,%a长度短些,显示 6 位有效数字,且会进行四舍五入
日期时间的格式化
- 占位符格式:
%[index$]t转换符 -
日期转换符:
c:星期六 十月 27 14:21:20 CST 2007F:2007-10-27D:10/27/07r:02:25:51 下午T:14:28:16R:14:28b:月份简称B:月份全称a:星期简称A:星期全称C:年前两位(不足两位补零)y:年后两位(不足两位补零)j:当年的第几天m:月份(不足两位补零)d:日期(不足两位补零)e:日期(不足两位不补零)
-
时间转换符:
H:24 小时制的小时(不足两位补零)k:24 小时制的小时(不足两位不补零)I:12 小时制的小时(不足两位补零)i:12 小时制的小时(不足两位不补零)M:分钟(不足两位补零)S:秒(不足两位补零)L:毫秒(不足三位补零)N:毫秒(不足 9 位补零)p:小写字母的上午或下午标记,如中文为“下午”,英文为 pmz:相对于 GMT 的时区偏移量,如 +0800Z:时区缩写,如 CSTs:自 1970-1-1 00:00:00 起经过的秒数Q:自 1970-1-1 00:00:00 起经过的毫秒
Loops⚓︎
while循环和do-while循环基本上和 C/C++ 一致-
带标签的
break语句,实现智能跳出语句块(而非跳入)- 标签可用在任何语句中,甚至在
if或块语句中
- 标签可用在任何语句中,甚至在
-
for循环也和 C/C++,其中 Java 5 引入了更加简洁的for-in语法,用于数组和容器:
Methods⚓︎
方法(methods) 是一个被分组在一起以执行某一操作的语句集合。
- 方法签名(method signature):方法名和参数列表的组合
- 形式参数(formal parameters)(形参
) :方法头中定义的变量 - 实际参数(actual parameters)(实参
) :调用方法时传递的参数值 - 返回值类型
Java 方法是按值传参的,传递的是变量或引用的副本,所以在方法内对参数修改不会改变方法外的变量或引用。
方法的重载(overloading) 是指在一个类中可以定义多个名称相同但参数列表不同的方法。注意区分重载和重写(overriding):
| 特性 | 方法重载 | 方法重写 |
|---|---|---|
| 发生范围 | 同一个类中 | 父子类之间 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回值 | 可以不同 | 必须相同(或其子类) |
| 判定时间 | 编译阶段 | 运行阶段 |
模糊调用(ambiguous invocation):有时对于方法的调用可能会有两个或更多可能的匹配,但编译器无法确定最具体的匹配,这是一个编译错误。可能的原因有:
- 方法重载时,多个方法的参数列表可能存在一定的重叠,导致编译器无法确定调用哪个方法
- 方法调用中涉及自动类型转换或可变参数时,可能会有多个方法同时符合调用条件
例子
public class AmbiguousOverloading {
public static void main(String[] args) {
System.out.println(max(1, 2));
}
public static double max(int num1, double num2) {
if (num1 > num2)
return num1;
else
return num2;
}
public static double max(double num1, int num2) {
if (num1 > num2)
return num1;
else
return num2;
}
}
Arrays⚓︎
数组(array) 是一种表示相同类型数据集合的数据结构。
Single-Dimensional Arrays⚓︎
-
数组变量声明:
-
创建数组:
arrayRefVar = new datatype[arraySize]; - 一步完成数组的声明和创建:
datatype[] arrayRefVar = new datatype[arraySize]; - 被
final的数组不可以指向其他数组对象,但数组元素是可变的 -
数组创建后长度固定不变,其值为
rrayRefVar.length;并且所有元素会被赋予默认值- 数值基础类型:
0 char类型:\u0000boolean类型:false
- 数值基础类型:
-
数组长度:
arrayRefVar.length - 通过索引访问数组元素:
arrayRefVar[index],index范围为[0, arrayRefVar.length-1] -
数组初始化器(intializer):一步完成声明、创建和初始化的简写语法,注意一定要在一条语句内完成,不能拆成多句
-
数组分配在堆上
[]运算符被预定义为检查数组边界,而且没有指针运算,就不能通过a+1得到下一个元素main函数中的String[] args参数表示命令行参数;注意args[0]就是第一个参数,程序名没有存储在args中-
复制数组:
-
如果直接使用赋值语句(比如
list2 = list1;) ,那么这只是让list2同时引用了list1的对象,并没有真的创建数组副本(浅拷贝) -
正确实现(深拷贝
) :-
手动实现:新建数组,利用循环逐元素复制
-
System.arraycopy(sourceArray, src_pos, targetArray, tar_pos, length)函数- 例子:
System.arraycopy(sourceArray, 0, targetArray, 0, sourceArray.length);
- 例子:
-
Arrays.copyOf(array, length)方法- 第 2 个参数是新数组的长度,这个方法通常用来改变数组的大小
- 如果数组元素是数值型,则多余的元素被赋值为
0;若是布尔型,则赋值为false - 如果长度小于原始数组长度,则只拷贝前面的元素
- 如果数组元素是数值型,则多余的元素被赋值为
-
例子:
- 第 2 个参数是新数组的长度,这个方法通常用来改变数组的大小
-
-
-
匿名数组(anonymous array):数组没有对应的显式引用变量
- 例子
: printArray(new int[]{3, 1, 2, 6, 4, 2});(printArray()是自定义函数)
- 例子
-
虽说 Java 按值传递参数,但对于数组这种引用类型而言,复制到方法中的参数值是指向数组实际内容的引用,因此修改数组参数是会实际影响到数组内容的
MultiDimensional Arrays⚓︎
-
声明 / 创建二维数组:
-
数组初始化器:使用简写记号完成声明 + 创建 + 初始化
-
长度:
-
二维数组的每一行可以有不同的长度,这样的数组称为锯齿状数组(ragged array)
评论区



