Java 基本语法

一、运算符

算术运算符(重点看 / % ++ )

%运算符

作用:取模、取余

本质:a % b = a - a / b * b

++运算符

作用:自增

作为独立式使用

  • 前++和后++完全等价于i = i + 1
1
2
3
4
5
6
//自增
i++;
// 等价于
++i;
// 等价于
i = i + 1;

作为表达式使用

  • 前++(++i): 先自增后赋值
  • 后++(i++): 先赋值后自增
1
2
3
4
int j = 8;
int k = ++j; //等价于 j=j+1; k=j; --> j==9, k==9
int j = 8;
int k = j++; //等价于 k=j; j=j+1; --> j==9, k==8

面试题

1
2
3
4
5
6
7
8
9
10
11
//面试题1:
int i = 1; //i -> 1
i = i++; // 规则使用临时变量:(1)temp = i; (2)i = i + 1; (3)i = temp;
System.out.println(i) // 1

//面试题2:
int i = 1; // i -> 1
i = ++i; // (1)i = i + 1; (2)temp = i; (3)i = temp;
System.out.println(i) // 2

// tips:自己给自己赋值要用到临时变量

关系运算符

==!=<><=>=instanceof
相等于不等于小于大于小于等于大于等于检查是否是类的对象

结果都是boolean值,true 或 false

逻辑运算符

(1)短路与 &&,短路或 ||,取反 !

(2)逻辑与 &,逻辑或 |,逻辑异或^

aba&ba&&ba|ba||b!aa^b
××
××××
×××
×××××××
  1. 结果都是boolean值,truefalse
  2. 开发中一般使用短路操作 &&||,效率高
    • 短路与&&:如果第一个条件为false,则第二个条件不会判断,最终结果为false
    • 短路或||:如果第一个条件为true,则第二个条件不会判断,最终结果为true

示例1:

1
2
3
4
5
int x = 5, y = 5;
if (x++==6 & ++y==6) { //x先比较后自增,第一个条件为false,y先自增后比较,第二个条件为true
x = 11;
}
System.out.println("x=" + x + ",y=" + y); // x=6, y=6

赋值运算符

赋值运算符就是将某个运算后的值,赋给指定的变量

  • 基本赋值运算符:=
  • 复合赋值运算符:+=-=/=%=

特点:

  1. 左边是变量,右边可以是变量、表达式、常量值
  2. a += b; 等价于 a = a + b; 其他以此类推
  3. 复合赋值运算符会自动进行类型转换

示例2:

1
2
3
byte b = 2;
b += 3; // 等价于 b = (byte)(b + 3)
b++; // 等价于 b = (byte)(b + 1)

三元运算符

基本语法:条件表达式 ? 表达式1 : 表达式2;

运算规则:如果条件表达式为true,运算后的结果是表达式1;反之false结果为表达式2(口诀:一眼丁真)

示例3:

1
2
3
4
5
6
7
8
int a = 8, b = 24;

// 由于true,执行了表达式1(a++),不执行表达式2(b--),
// 因此a先返回值给res再自增,b--不执行
int res = a < b ? a++ : b--;

System.out.println("a=" + a + "b=" + b + "res=" + res)
//a=9,b=24,res=8

细节:

  1. 表达式1 和 表达式2 要为可以赋给接收变量的类型(或可以自动转换/ 或强制转换)
  2. 三元运算符本质是 if–else 语句
1
2
3
4
int res = a > b ? a++ : --b;
等价于
if (a > b) res = a++;
else res = --b;

⚠️区别:

三元运算符是一个整体!if 和 else 是独立语句块!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//由于三元运算符是一个整体,表达式1 和 表达式2 要自动转换为同一种类型
Object obj1 = true ? new Integer(1) : new Double(2.0);
System.out.println(obj1); //输出:1.0

// 根据编译器提示: "true ? new Integer(1) : new Double(2.0)"
// 可以简化为-> "(double) new Integer(1)"
// 可以移除包装-> "(double) 1"

//---------------------------------------

Object obj2;
if(true)
obj2 = new Integer(1);
else
obj2 = new Double(2.0);
System.out.println(obj2); //输出:1

运算符优先级

运算符优先级

二、进制转换

整数的四种表达方式

二进制(BIN):0,1,以0b0B开头。 int n1 = 0b1010;

十进制(DEC):0-9int n2 = 1010;

八进制(OCT):0-7,以数字0开头。 int n3 = 01010;

十六进制(HEX):0-9A(10)-F(15);以0x0X开头。 int n4 = 0x10101;

Java 进制转换

1
2
3
4
5
6
// 十转二
bin_num = Integer.toBinaryString(dec_num);
// 十转八
oct_num = Integer.toOctalString(dec_num);
// 十转十六
hex_num = Integer.toHexString(dec_num);

二进制(八进制、十六进制)转十进制

从最低为(右边)开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和。

例:0b1011 = 1 * 20 + 1 * 21 + 0 * 82 + 1 * 23 = 1 + 2 + 0 + 8 = 11

从最低为(右边)开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和。

例:0234 = 4 * 80 + 3 * 81 + 2 * 82 = 4 + 24 + 128 = 156

从最低为(右边)开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和。

例:0x23A = 10 * 160 + 3 * 161 + 2 * 162 = 10 + 48 + 512 = 570

十进制转二进制(八进制、十六进制同理)

除2取余,逆序排列”:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来。

例:十进制34转成二进制: =0B00100010

十进制转二进制

二进制转八进制(十六进制)

从低位开始,将二进制数每三位(每四位)一组,转成对应的八进制(十六进制)数即可。

例:0b1101(D)0101(5) = 0xD5

二转十六进制诀窍:二进制每四位视为“8421”码,1101即为1个8,1个4,0个2,1个1相加,结果为13,即D

八进制(十六进制)转二进制

八进制数每1位,转成对应的一个3位二进制数。

例:0237转成二进制:02(010)3(011)7(111) = 0b10011111

十六进制数每1位,转成对应的一个4位二进制数。

例:0x23B转成二进制:0x2(0010)3(0011)B(1011) = 0b001000111011 (快速计算”B”的二进制技巧:11 = 15(1111) - 4(0100) = B = 11(1011) )

原码、反码、补码

原码表示范围:-127~127

反码表示范围:-127~127

补码表示范围:-128~127

总结

  1. 二进制的最高位是符号位:0表示正数,1表示负数()
  2. 正数的原码、反码、补码都一样(正数三码合一)
  3. 负数的反码 = 它的原码符号位不变,其他位取反(0->1 1->0)
  4. 负数的补码 = 它的反码 + 1(负数的反码 = 它的补码 - 1)(补 = 反 + 1)
  5. 负数的原码 = 【它的反码 符号位不变,其他位取反】 = 【(它的补码 - 1)符号位不变,其他位取反】= 【它的绝对值(正数)的原码符号位变为0】
  6. 负数 = 其绝对值的原码最高位取1(原码)的 补码(原码取反+1)
  7. 0的反码、补码都是0
  8. java没有无符号数
  9. 在计算机运算的时候,都是以补码的方式来运算的
  10. 当我们看运算结果时,要看它的原码

计算机中,负数表达形式

负数二进制以补码形式表达,

  1. 把这个负数的绝对值转换为二进制,即求原码 ( |-24| = 24 = 00011000 )
  2. 把原码取反,即求反码( ~00011000 = 11100111 )
  3. 把反码加1,即求补码( 11101000 = -24(补码) )

位运算

  1. java中7个位运算:&、|、^、~、>>、<<和>>>
  2. 按位与&:两位为1,结果为1,否则为0
  3. 按位或|:有一个为1,则结果为1,否则为0
  4. 按位异或^:同0异1
  5. 按位取反~:0变1,1变0

示例:

~-2的结果:

1
System.out.println(~-2); // 1
  1. 先得到-2的原码:10000000 00000000 00000000 00000010
  2. 再得-2的反码: 11111111 11111111 11111111 11111101(=原码符号位不变,其他位取反)
  3. 再得-2的补码: 11111111 11111111 11111111 11111110(=反码+1)
  4. -2操作: 00000000 00000000 00000000 00000001(-2:对-2的补码进行取反操作)
  5. 运算后的原码: 00000000 00000000 00000000 00000001(1)

示例:

~2的结果:

1
System.out.println(~2); //-3
  1. 先得到2的补码:00000000 00000000 00000000 00000010(正数三码都一样)
  2. 2操作: 11111111 11111111 11111111 11111101(运算后的补码)(2:对2的补码进行取反操作)
  3. 运算后的反码: 11111111 11111111 11111111 11111100(负数反码 = 它的补码-1)
  4. 运算后的原码: 10000000 00000000 00000000 00000011(-3)

算术右移>>:向右移若干位,符号位不变,低位溢出丢弃,高位补符号位(符号位补溢出的高位,即正数高位补 0,负数高位补 1)

x >> n,相当于 x 除以 2 的 n 次方。

1
2
3
4
5
byte 15>>2;
// 15 => 0000 1111 => 0000 0011
// 本质 15 / 2 / 2 = 3
byte b = -15 >> 2;
// 1111 0001 >> 0000 0010 = 1111 1100(-4)

算术左移<<:向左移若干位,符号位不变,低位补0,高位丢弃。

x << n,相当于 x 乘以 2 的 n 次方(不溢出的情况下)

1
2
3
byte 4<<3; 
//4 => 0000 0100 -> 0010 0000
// (本质 4 * 2 * 2 * 2 = 32) (4<<3 => 4 * 2^3)

无符号右移>>>:忽略符号位,空位都以0补齐。

移位操作符实际上只支持intlong类型,编译器在对shortbytechar类型进行移位前,都会将其转换为int类型再操作。

移位的位数超过数值所占有的位数

int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。

也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。

当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。

也就是说:x<<42等同于x<<10x>>42等同于x>>10x >>>42等同于x >>> 10

1
2
3
4
5
6
int i = -1;
System.out.println("初始数据:" + i);
System.out.println("初始数据对应的二进制字符串:" + Integer.toBinaryString(i));
i <<= 42; // 等价于 i <<= 10; 42 % 32 = 10
System.out.println("左移 10 位后的数据 " + i);
System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));

输出:

1
2
3
4
初始数据:-1
初始数据对应的二进制字符串:11111111111111111111111111111111
左移 10 位后的数据 -1024
左移 10 位后的数据对应的二进制字符 11111111111111111111110000000000

问题:为何使用原码、反码、补码

我们上面说过,原码、反码、补码的表示对于正数来说都是一样的,而对于负数来说,三种码的表示确是完全不同的,那大家是否会有个疑问:如果原码才是我们人类可以识别并用于直接计算的表示方式,那为什么还会有反码和补码?计算机直接存储原码不就完事了?

在解决这些问题前,我们先来了解计算机的底层概念,我们人脑可以很轻松的知道机器数的第一位是符号位,但对于计算机基础电路设计来说判别第一位是符号位是非常难和复杂的事情,为了让计算机底层设计更加简单,人们开始探索将符号位参与运算,并且采用只保留加法的方法,我们知道减去一个数,等于加上这个数的负数,即:1-1 = 1 + (-1) = 0,这样让计算机运算就更加简单了,并且也让符号位参与到运算中去。

使用原码运算

计算十进制表达式:1-1 = 0

1
1 - 1 = 1 + (-1)

= 原:[ 0000 0001 ] + 原:[ 1000 0001 ]

= 原:[ 1000 0010 ] = -2

如果用原码表示,让符号位也参与计算,对于减法来说,结果是不正确的。这也是计算机内部在存储数据时不使用原码的原因,为了解决这一问题,出现了反码。

使用反码运算

计算十进制表达式:1-1 = 0

1
1 - 1 = 1 + (-1)

= 原:[ 0000 0001 ] + 原:[ 1000 0001 ]

= 反:[ 0000 0001 ] + 反:[ 1111 1110 ]

= 反:[ 1111 1111 ] = 原: [ 1000 0000 ] = -0

通过计算我们发现用反码计算减法,结果的真值部分是正确的。而唯一的问题出现在”0”这个特殊的数值上,虽然人们理解上**+0和-0**是一样的,但是0带符号是没有任何意义的,而且会有 [0000 0000] 原和 [1000 0000] 原两个编码表示0。为了解决这一问题,出现了补码。

使用补码运算

1
1 - 1 = 1 + (-1)

= 原:[ 0000 0001 ] + 原:[ 1000 0001 ]

= 补:[ 0000 0001 ] + 补:[ 1111 1111 ]

= 补: [ 0000 0000 ] = 原: [ 0000 0000 ] = 0

这样0用 [0000 0000] 表示,而以前出现问题的-0则不存在了,而且人们还发现可以用[1000 0000]表示-128,-128的推算过程如下:

(-1) + (-127) = -128

= 原:[1000 0001] + 原:[ 1111 1111 ]

= 补:[ 1111 1111 ] + 补:[ 1000 0001 ]

= 补:[ 1000 0000 ]

注意:因为实际上是使用以前的-0的补码来表示-128,所以**-128并没有原码和反码表示**,只要补码是[1000 0000],其十进制数值就为-128。

因为补码能多存储一个-128,而且在计算机底层中存储的是补码,所以在计算机中一个8位的二进制数的存储范围是用补码表示的[-128,127],而不是用原码或反码表示的[-127,127]。这也可以解释为什么计算机中一个字节的取值范围是[-128,127]。

这样也能够回答我们开始提出的问题了,原码、反码、补码的使用,是人们为了让符号位能参与运算并让计算机底层运算更加简单而设计出来的数据存储表示方式。

三、标识符与关键字

标识符就是一个名字。(标识符:阿牛茶餐厅、美宜佳)

关键字是被赋予特殊含义的标识符。(关键字:医院、警察局、学校…)

default关键字

default 这个关键字很特殊,既属于程序控制,也属于类,方法和变量修饰符,还属于访问控制。

  • 在程序控制中,当在 switch 中匹配不到任何情况时,可以使用 default 来编写默认匹配的情况。
  • 在类,方法和变量修饰符中,从 JDK8 开始引入了默认方法,可以使用 default 关键字来定义一个方法的默认实现。
  • 在访问控制中,如果一个方法前没有任何修饰符,则默认会有一个修饰符 default,但是这个修饰符加上了就会报错。

⚠️ 注意:虽然 true, false, 和 null 看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。

官方文档:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

访问修饰符

java提供了四种访问控制修饰符号,用于控制方法和属性的访问权限:

  1. 公开级别:用public修饰,对外公开;
  2. 受保护级别:用protected修饰,对子类和同一个包中的类公开;
  3. 默认级别:没有修饰符号,向同一个包的类公开;
  4. 私有级别:用private修饰,只有类本身可以访问,不对外公开。

访问范围

访问级别访问控制修饰符同类同包子类不同包
公开public
受保护protected×
默认-××
私有private×××

使用

  1. 修饰符可以用来修饰类中的属性、成员方法以及类;
  2. 只有默认的和public才能修饰类,并且遵循上述访问权限的特点
  3. 子类
  4. 成员方法的访问规则和属性一样

final 关键字

可以修饰类、属性、方法、局部变量。

适用场景

1)当不希望类被继承时;

2)不希望父类的某个方法被子类覆盖/重写override时;

3)当不希望类的某个属性的值被修改时;

4)当不希望某个局部变量被修改时。

规则

1)final修饰的属性又叫常量,一般用XX_XX_XX命名

2)final修饰的属性在定义时,必须赋初值,赋值可以在类定义属性时、构造器中、代码块中;

3)如果final修饰的属性是静态的,则初始化的位置只能是定义时、在静态代码块中(不能在构造器中赋值)

4)如果一个类已经是final类,就没有必要将方法修饰成final了。

5)final不能修饰构造器。

6)**final****static**往往搭配使用,不会导致类加载(底层编译器做了优化处理),效率更高

7)包装类(IntegerDouble等都是final),String也是final类。

四、控制结构

顺序控制

程序从上到下逐行执行,中间没有任何判断和跳转。

Java定义变量时采用合法的前向引用。如:

1
2
int a = 1;
int b = a + 1;

分支控制if-else

让程序有选择的执行,分支控制有三种(单分支、双分支、多分支)

嵌套分支:分支嵌套最好不要超过3层(可读性差)

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//单分支:当条件表达式为true时,就会执行{ }的代码。(如果{}中只有一条语句,可以省略{},但建议写上)
if(条件表达式){
执行代码块;
}

//双分支:当条件表达式为true时,就会执行{代码块1},否则执行{代码块2}。(如果{代码块}中只有一条语句,可以省略{},但建议写上)
if (条件表达式) {
代码块1;
}
else {
代码块2;
}

//多分支:当条件表达式1为true时,就会执行{代码块1},当条件表达式1为false时,再判断条件表达式2是否成立,以此类推,都不成立则执行{代码块n}。(多分支可以没有else)
if (条件表达式1) {
代码块1;
}
else if (条件表达式2) {
代码块2;
}
......
else {
代码块n;
}

switch分支结构

规则:

  1. case 语句中的value的数据类型必须与表达式变量的数据类型相同,而且只能是常量或者字面常量;(或是表达式可以自动转换成可以与casevalue相互比较的类型。)
  2. switch表达式中的返回值必须是:byteshortintcharenumString
  3. 表达式对应一个值;
  4. 当变量的值与 case 语句的值相等时,那么 case 语句之后的语句开始执行,直到 break 语句出现才会跳出 switch 语句;
  5. case常量1:当表达式的值等于value1,就执行语句块1;如果没有匹配value1,就继续匹配case value2;如果一个都没匹配上,执行default
  6. case 语句不必须要包含 break 语句。如果没有 break 语句出现,程序会继续执行下一条 case 语句,直到出现 break 语句。(穿透)。

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
switch (expression) {
case value1:
//语句块1; //可省略
break; //可选
case value2:
//语句块2;
break;
...
default:
//default语句块;
break;
}

switch控制结构

for 循环控制

基本规则:

  1. 循环初始值可以有多个,但类型必须相同;
  2. 初始化和变量迭代可以写到其他地方,但是分号不能省略;(见示例)
  3. 循环条件是布尔表达式。
  4. 循环四要素:循环变量初始化;循环条件;循环语句;循环变量迭代。

基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
for(循环变量初始化; 循环条件(布尔表达式); 循环变量迭代/更新) {
//循环操作(可以多条);
}

//示例:
int i = 1; //变量初始化(变量i的作用域更大)
for (; i<10; ) {
System.out.println("shit" + i);
i++; //变量迭代
}
//补充
for(;;){ //死循环
}

for each 循环(增强for)

foreach 循环语句是 Java1.5 的新特征之一,在遍历数组、集合方面,foreach 为开发者提供了极大的方便。foreach 循环语句是 for 语句的特殊简化版本,主要用于执行遍历功能的循环。

1
2
3
for (元素类型 元素变量名x : 遍历对象obj) {   // 快捷键:大写I + 回车
//引用了x的java语句;
}

其中,“类型”为元素的类型,“元素变量名”表示对象的每一个元素,“遍历对象”表示被遍历的对象,如集合。

每执行一次循环语句,循环变量就读取集合中的一个元素。

while 循环控制

基本规则:

  1. while循环是先判断 再执行语句;
  2. 只要布尔表达式为 true,循环就会一直执行下去;
  3. 循环条件是布尔表达式;
  4. 也有循环四要素。

基本语法:

1
2
3
4
5
int i = 1;  //循环变量初始化
while (i <= 10) { //循环条件
xxx //循环体(语句)
i++; //循环变量迭代
}

do…while 循环控制

基本规则:

  1. do while 是关键字;
  2. 先执行,再判断,也就是说,一定会至少执行一次;
  3. 也有四要素,只是位置不一样;
  4. 最后有一个分号。

基本语法:

1
2
3
4
do{
System.out.print("value of x : " + x ); //循环体(语句)
x++; //循环变量迭代
} while(x < 8); //(循环条件)

多重循环

实质上,嵌套循环就是把内层循环当成外层循环的循环体。

当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次循环。

设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次

continuebreakreturn

在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:

  1. continue:指跳出当前的这一次循环,继续下一次循环。
  2. break:指跳出整个循环体,继续执行循环下面的语句。

return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:

  1. return;:直接使用 return 结束方法执行,用于没有返回值函数的方法
  2. return value;:return 一个特定值,用于有返回值函数的方法

break 跳转控制语句

主要用在循环语句或者 switch 语句中,用来跳出整个语句块

break语句出现仔多层嵌套的语句块中时,可以通过标签指明要终止的哪一层语句块。

注意事项:

  1. break语句可以指定退出哪一层;
  2. label1是标签,名字由程序员指定;
  3. break 后指定到哪个label 就退出到哪里;
  4. 在实际开发中,尽量不要用标签;
  5. 如果没有指定,break默认退出最近的循环体。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int i = 0;

lable1:
for( int i = 0; i < 4; i++ ) {

lable2:
for( int j = 0; j < 10; j++ ) {
if( j == 2 ){break;} //等价于 break lable2
System.out.print(j);
}

}
//输出:
01010101
//(如果break label1; 则输出结果为:01)

跳转控制语句 continue

基本规则:

  1. continue 语句用于结束本次循环,继续执行下一次循环(作用是让程序立刻跳转到下一次循环的迭代)
  2. continue 语句出现在多层嵌套循环语句体中时,可以通过标签指明要跳过的是哪一层循环(实际开发仍不建议使用)
  3. 在 for 循环中,continue 语句使程序立即跳转到更新语句。(i++)
  4. 在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。(i>0)

基本语法:

1
2
3
4
5
6
7
8
9
int [] numbers = {1,2,3,4,5,6,7,8};

for( int i : numbers ) {
if( i == 4 ) {
continue;
}
System.out.print(i);
}
//输出:1235678

return

  • return用在方法时,表示跳出方法;
  • 如果用在main方法,表示退出程序。

六、注释

Java 中的注释有三种:

  • 单行注释:通常用于解释方法内某单行代码的作用。
  • 多行注释:通常用于解释一段代码的作用。(不常用)
  • 文档注释:通常用于生成 Java 开发文档。

代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。


Java 基本语法
https://blog-21n.pages.dev/2022/05/30/Java-基本语法/
作者
Neo
发布于
2022年5月30日
许可协议