基本语法
注释有哪几种形式?
Java 中的注释有三种:
-
单行注释:通常用于解释方法内某单行代码的作用。
-
多行注释:通常用于解释一段代码的作用。
-
文档注释:通常用于生成 Java 开发文档。
用的比较多的还是单行注释和文档注释,多行注释在实际开发中使用的相对较少。

在我们编写代码的时候,如果代码量比较少,我们自己或者团队其他成员还可以很轻易地看懂代码,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释),是我们程序员写给自己看的,注释是你的代码说明书,能够帮助看代码的人快速地理清代码之间的逻辑关系。因此,在写程序的时候随手加上注释是一个非常好的习惯。
《Clean Code》这本书明确指出:
代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。
若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。
举个例子:
去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可
// check to see if the employee is eligible for full benefits if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))应替换为
if (employee.isEligibleForFullBenefits())
标识符和关键字的区别是什么?
在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了 标识符 。简单来说, 标识符就是一个名字 。
有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这些特殊的标识符就是 关键字 。简单来说,关键字是被赋予特殊含义的标识符 。比如,在我们的日常生活中,如果我们想要开一家店,则要给这个店起一个名字,起的这个“名字”就叫标识符。但是我们店的名字不能叫“警察局”,因为“警察局”这个名字已经被赋予了特殊的含义,而“警察局”就是我们日常生活中的关键字。
Java 语言关键字有哪些?
| 分类 | 关键字 | ||||||
|---|---|---|---|---|---|---|---|
| 访问控制 | private | protected | public | ||||
| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
| new | static | strictfp | synchronized | transient | volatile | enum | |
| 程序控制 | break | continue | return | do | while | if | else |
| for | instanceof | switch | case | default | assert | ||
| 错误处理 | try | catch | throw | throws | finally | ||
| 包相关 | import | package | |||||
| 基本类型 | boolean | byte | char | double | float | int | long |
| short | |||||||
| 变量引用 | super | this | void | ||||
| 保留字 | goto | const |
Tips:所有的关键字都是小写的,在 IDE 中会以特殊颜色显示。
default这个关键字很特殊,既属于程序控制,也属于类,方法和变量修饰符,还属于访问控制。
- 在程序控制中,当在
switch中匹配不到任何情况时,可以使用default来编写默认匹配的情况。- 在类,方法和变量修饰符中,从 JDK8 开始引入了默认方法,可以使用
default关键字来定义一个方法的默认实现。- 在访问控制中,如果一个方法前没有任何修饰符,则默认会有一个修饰符
default,但是这个修饰符加上了就会报错。
⚠️ 注意:虽然 true, false, 和 null 看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。
官方文档:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
⭐️自增自减运算符
在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1。Java 提供了自增运算符 (++) 和自减运算符 (--) 来简化这种操作。
++ 和 -- 运算符可以放在变量之前,也可以放在变量之后:
- 前缀形式(例如
++a或--a):先自增/自减变量的值,然后再使用该变量,例如,b = ++a先将a增加 1,然后把增加后的值赋给b。 - 后缀形式(例如
a++或a--):先使用变量的当前值,然后再自增/自减变量的值。例如,b = a++先将a的当前值赋给b,然后再将a增加 1。
为了方便记忆,可以使用下面的口诀:符号在前就先加/减,符号在后就后加/减。
flowchart LR
%% 定义全局样式
classDef step fill:#4CA497,color:#fff,rx:10,ry:10
classDef example fill:#E99151,color:#fff,rx:10,ry:10
subgraph Prefix["前缀形式 ++a / --a"]
direction TB
style Prefix fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
P1["第一步:变量自增/自减"]:::step --> P2["第二步:使用新值参与运算"]:::step
P3["示例:b = ++a<br先 a=a+1,再 b=a"]:::example
end
subgraph Suffix["后缀形式 a++ / a--"]
direction TB
style Suffix fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
S1["第一步:使用当前值参与运算"]:::step --> S2["第二步:变量自增/自减"]:::step
S3["示例:b = a++<br先 b=a,再 a=a+1"]:::example
end
linkStyle default stroke-width:1.5px,opacity:0.8下面来看一个考察自增自减运算符的高频笔试题:执行下面的代码后,a 、b 、 c 、d和e的值是?
int a = 9;
int b = a++;
int c = ++a;
int d = c--;
int e = --d;答案:a = 11 、b = 9 、 c = 10 、 d = 10 、 e = 10。
⭐️移位运算符
移位运算符是最基本的运算符之一,几乎每种编程语言都包含这一运算符。移位操作中,被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。
移位运算符在各种框架以及 JDK 自身的源码中使用还是挺广泛的,HashMap(JDK1.8) 中的 hash 方法的源码就用到了移位运算符:
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^:按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
使用移位运算符的主要原因:
- 高效:移位运算符直接对应于处理器的移位指令。现代处理器具有专门的硬件指令来执行这些移位操作,这些指令通常在一个时钟周期内完成。相比之下,乘法和除法等算术运算在硬件层面上需要更多的时钟周期来完成。
- 节省内存:通过移位操作,可以使用一个整数(如
int或long)来存储多个布尔值或标志位,从而节省内存。
移位运算符最常用于快速乘以或除以 2 的幂次方。除此之外,它还在以下方面发挥着重要作用:
- 位字段管理:例如存储和操作多个布尔值。
- 哈希算法和加密解密:通过移位和与、或等操作来混淆数据。
- 数据压缩:例如霍夫曼编码通过移位运算符可以快速处理和操作二进制数据,以生成紧凑的压缩格式。
- 数据校验:例如 CRC(循环冗余校验)通过移位和多项式除法生成和校验数据完整性。
- 内存对齐:通过移位操作,可以轻松计算和调整数据的对齐地址。
掌握最基本的移位运算符知识还是很有必要的,这不光可以帮助我们在代码中使用,还可以帮助我们理解源码中涉及到移位运算符的代码。
flowchart TB
%% 定义全局样式,保持统一风格
classDef left fill:#4CA497,color:#fff,rx:10,ry:10
classDef right fill:#00838F,color:#fff,rx:10,ry:10
classDef uright fill:#E99151,color:#fff,rx:10,ry:10
subgraph ShiftOps["Java 三种移位运算符"]
direction TB
style ShiftOps fill:#F0F2F5,stroke:#E0E6ED,stroke-width:1.5px
subgraph Left["左移 <<"]
style Left fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
L1["操作:向左移动 n 位"]:::left
L2["规则:高位丢弃,低位补 0"]:::left
L3["效果:相当于 × 2^n"]:::left
L4["示例:8 << 2 = 32"]:::left
end
subgraph Right["带符号右移 >>"]
style Right fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
R1["操作:向右移动 n 位"]:::right
R2["规则:低位丢弃,高位补符号位"]:::right
R3["效果:相当于 ÷ 2^n"]:::right
R4["示例:-8 >> 2 = -2"]:::right
end
subgraph URight["无符号右移 >>>"]
style URight fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
U1["操作:向右移动 n 位"]:::uright
U2["规则:低位丢弃,高位补 0"]:::uright
U3["效果:逻辑右移"]:::uright
U4["示例:-8 >>> 2 = 1073741822"]:::uright
end
end
linkStyle default stroke-width:1.5px,opacity:0.8Java 中有三种移位运算符:
<<,向左移若干位,高位丢弃,低位补零。x << n,相当于 x 乘以 2 的 n 次方(不溢出的情况下)。>>,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> n,相当于 x 除以 2 的 n 次方。>>>,忽略符号位,空位都以 0 补齐。
虽然移位运算本质上可以分为左移和右移,但在实际应用中,右移操作需要考虑符号位的处理方式。
由于 double,float 在二进制中的表现比较特殊,因此不能来进行移位操作。
移位操作符实际上支持的类型只有int和long,编译器在对short、byte、char类型进行移位前,都会将其转换为int类型再操作。
如果移位的位数超过数值所占有的位数会怎样?
当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。
也就是说:x<<42等同于x<<10,x>>42等同于x>>10,x >>>42等同于x >>> 10。
左移运算符代码示例:
int i = -1;
System.out.println("初始数据:" + i);
System.out.println("初始数据对应的二进制字符串:" + Integer.toBinaryString(i));
i <<= 10;
System.out.println("左移 10 位后的数据 " + i);
System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));输出:
初始数据:-1
初始数据对应的二进制字符串:11111111111111111111111111111111
左移 10 位后的数据 -1024
左移 10 位后的数据对应的二进制字符 11111111111111111111110000000000由于左移位数大于等于 32 位操作时,会先求余(%)后再进行左移操作,所以下面的代码左移 42 位相当于左移 10 位(42%32=10),输出结果和前面的代码一样。
int i = -1;
System.out.println("初始数据:" + i);
System.out.println("初始数据对应的二进制字符串:" + Integer.toBinaryString(i));
i <<= 42;
System.out.println("左移 10 位后的数据 " + i);
System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));右移运算符使用类似,篇幅问题,这里就不做演示了。
continue、break 和 return 的区别是什么?
在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
continue:指跳出当前的这一次循环,继续下一次循环。break:指跳出整个循环体,继续执行循环下面的语句。
return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
return;:直接使用 return 结束方法执行,用于没有返回值函数的方法return value;:return 一个特定值,用于有返回值函数的方法
flowchart TB
subgraph Method["方法体"]
direction TB
style Method fill:#F5F7FA,stroke:#E0E6ED,stroke-width:1.5px
Start["方法开始"] --> Loop
subgraph Loop["循环体 for/while"]
direction TB
style Loop fill:#F0F2F5,stroke:#E0E6ED,stroke-width:1.5px
L1["循环条件判断"] -->|"满足"| L2["执行循环体"]
L2 --> L3{{"遇到关键字?"}}
L3 -->|"continue"| Continue["跳过本次<br/>继续下一次循环"]
L3 -->|"break"| Break["跳出整个循环"]
L3 -->|"无"| L1
Continue --> L1
end
Break --> AfterLoop["循环后的代码"]
L1 -->|"不满足"| AfterLoop
AfterLoop --> L4{{"遇到 return?"}}
L4 -->|"是"| Return["结束整个方法"]
L4 -->|"否"| End["方法正常结束"]
end
classDef start fill:#E99151,color:#fff,rx:10,ry:10
classDef loop fill:#4CA497,color:#fff,rx:10,ry:10
classDef decision fill:#00838F,color:#fff,rx:10,ry:10
classDef alert fill:#C44545,color:#fff,rx:10,ry:10
class Start,End start
class L1,L2,AfterLoop loop
class L3,L4 decision
class Continue,Break,Return alert
linkStyle default stroke-width:1.5px,opacity:0.8思考一下:下列语句的运行结果是什么?
public static void main(String[] args) {
boolean flag = false;
for (int i = 0; i <= 3; i++) {
if (i == 0) {
System.out.println("0");
} else if (i == 1) {
System.out.println("1");
continue;
} else if (i == 2) {
System.out.println("2");
flag = true;
} else if (i == 3) {
System.out.println("3");
break;
} else if (i == 4) {
System.out.println("4");
}
System.out.println("xixi");
}
if (flag) {
System.out.println("haha");
return;
}
System.out.println("heihei");
}运行结果:
0
xixi
1
2
xixi
3
haha
评论
使用 GitHub 账号即可参与加载较慢?可 直接前往 GitHub Discussions 查看与参与。