[java复习] 操作符和关键字

概要

这一章的复习主要涵常见的 运算符 及经常用到的 运算符操作关键字。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

操作符

算数运算符

在运算过程中会自动进行类型提升
1
2
3
int i = 1;
float f = 12.3F;
double d = i + f / 2;

算数运算符中重要的就是 + 操作符,以为它支持对 String 的操作用来连接字符串,式子中存在 String 类型的时候,最终类型会被转换为 String 类型,转换时机还是跟运算顺序有关,由结合律决定,详细的 String 会在后面的 String 中细致分析。

等价操作符

==!= 等价性操作符对于基本数据类型和对象分开操作

  • 基本类型 直接比较数值,例如:intdouble
  • 对象引用 比较引用是否相等,是否指向同一个对象

java 的基类 java.lang.Objectboolean equals(Object obj) 方法,就是使用了 == 操作符来进行比较是否等价
因此所有的 java 类型调用 equals(obj) 方法的时候,其实就调用了 == 来比较对象的,需要重写 equals(obj) 方法才能按照指定的方式来比较!StringInteger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
A a1 = new A(12);
A a2 = new A(12);
System.out.println(a1);
System.out.println(a2);

System.out.println(a1 == a2);
System.out.println(a1.equals(a2));

/* Output:

a1: com.example.review.operator.Equals$A@b4c966a
a2: com.example.review.operator.Equals$A@2f4d3709

a1 == a2: false
a1.equals(a2): true
*///:~

String 中重写了基类中的 equals(obj) 方法:

  1. 如果是同一个对象,直接返回 true
  2. 判断是否为 String 类型对象
  3. 判断编码格式是否相同
  4. 根据编码类型调用对应的比较方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// java 12 source

public class Object {

public boolean equals(Object obj) {
return (this == obj); // 默认使用 == 比较引用
}

}


public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {

public boolean equals(Object anObject) {
if (this == anObject) { // 引用相同直接 return true
return true;
}
if (anObject instanceof String) { // 判断类型是否为 String
String aString = (String)anObject;
if (coder() == aString.coder()) { // 判断编码格式时候相同
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value); // 调用编码对应的比较方法
}
}
return false;
}

}

直接常量

指定进制

format 进制
0b10 二进制
100 十进制(默认)
0144 八进制
0x64 十六进制

指定类型

format suffix type
1.2F f / F float
123L l / L long

在前面有提到整数默认类型为 int,非整数类型默认类型为 double

指数计数

e/E 用来表示 10 的幂次,默认为 double 类型

1
2
3
4
double d = 1.2e3;
System.out.println(d);

///:~ 1200.0

移位操作符

这部分应该是除了算数运算和比较运算符之外最多的了。

  1. >> 操作为 算数左/右移,左边的补位跟符号有关
    • 正数补 0
    • 负数补 1
  1. >>> 为逻辑右移,就当做直接右移就完事了。

通常使用的时候使用 >>> 逻辑右移,避免在处理负数上出现状况

1
int mid = lo + ((hi - lo) >>> 1);

自动递增和递减

前置递增没有什么特点,主要举例分析后置自动递增操作符的机制。

后置递增机制 (递减同理)

  1. 局部变量表中的值 压入 操作数栈
  2. 局部变量表的值 + 1
  3. 将结果从 操作数栈 中弹出,并赋值给 局部变量表
1
2
3
4
5
6
7
// java test

int i = 0;
i = i++; // 操作数栈中保存的原始的值,又重新赋值给了已经变化的局部变量表
System.out.println(i);

///:~ 0

之所以结果出现 i = 0 ,就是因为后置递增的机制的原因,i 在完成赋值之后又被重复赋值上了保存到 temp 中的原来的值。

将上述机制可以等效为

因为 java 中不能使用指针,无法完成基本类型传地址对原变量进行修改的操作,所以这里使用 c++ 来完成对上述机制的描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// c++ test

#include <iostream>
using namespace std;

int analogPostInc(int &i) {
int temp = i; // 1
i += 1; // 2
return temp; // 3
}

int main() {
int i = 0;
analogPostInc(i); // 模拟递增
cout << i << endl;

i = 0;
i = analogPostInc(i); // 模拟 i = i++;
cout << i << endl;
return 0;
}

位运算操作符

& 按位与的操作

  1. 取最后一个 1 表示的值

  2. 去掉二进制表示中的最后一个 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// java test

int i = 0b11110;
int tailOne = i & -i;
int eraserTailOne = i & (i - 1);

System.out.println("tailOne: " + Integer.toBinaryString(tailOne));
System.out.println("eraserTailOne: " + Integer.toBinaryString(eraserTailOne));

/* Output:

tailOne: 10
eraserTailOne: 11100
*///:~

~ 按位取反运算符

-x = ~x + 1 的证明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// java test

int x = 0b1;
int nx = ~x;
int ret = x + nx;

System.out.println("-x == ~x + 1: " + (-x == nx + 1));

System.out.println("x: " + Integer.toBinaryString(x));
System.out.println("nx: " + Integer.toBinaryString(nx));
System.out.println("ret: " + Integer.toBinaryString(ret));

/* Output:

-x == ~x + 1: true

x: 1
nx: 11111111111111111111111111111110
ret: 11111111111111111111111111111111

*///:~

^ 按位异或操作

^ 的性质就是相同的数进行异或为 0,这个性质就适用于

  • 一个数组除了 一个数出现奇数次 外,其他的数都出现了 偶数次,求这个数

操作符优先级

优先级 运算符 结合性
1 ( ) [ ] –>
2 ! + - ~ ++ -- <–
3 * / % –>
4 + - –>
5 << >> >>> –>
6 < <= > >= instanceof –>
7 == != –>
8 & –>
9 ^ –>
10 | –>
11 && –>
12 || –>
13 ? : <–
14 = += -= *= /= &= |= ^= ~= <<= >>= >>>= <–

关键字

package 关键字

必须是第一条代码

import 关键字

编译器遇到 import 关键字会从 classpath 中开始查找对应路径的 .class 文件。

this 关键字

this 当前对象引用

  1. this 关键字只 能用于方法中this 代表的是调用当前方法的对象实例

    方法返回 当前引用本身,参考 StringBuilder 中的 StringBuilder append(String )

    1
    2
    3
    4
    5
    6
    7
    8
    // java test

    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("A").append("B").append("C").append("D");

    System.out.println("str: " + stringBuilder);

    ///:~ str: ABCD

    StringBuilder 源码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // java 12 source

    public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuilder>, CharSequence {

    @Override
    @HotSpotIntrinsicCandidate
    public StringBuilder append(String str) {
    super.append(str);
    return this; // 返回当前对象引用
    }

    }
  2. this 可以用于区分同名的 形参类的成员变量

    if result
    成员变量
    不加 形参

    不使用 this 关键字修饰,默认情况下调用的为 形参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // java test


    public class This {

    private int value;

    private This() {}

    /*
    this 在构造器中调用构造器
    1. 必须在第一句
    2. 能且只能调用一次
    */
    private This(int value) {
    this();
    this.value = value;
    }

    private void paramWithoutThis(int value) {
    System.out.println("without: " + value);
    }

    private void paramUseThis(int value) {
    System.out.println("useThis: " + this.value);
    }

    private static void thisObjRef() {
    This _this = new This(1);
    System.out.println("_this.value: " + _this.value);

    _this.paramUseThis(2);
    _this.paramWithoutThis(2);

    /* Output:

    _this.value: 1
    useThis: 1
    without: 2
    *///:~
    }

    }
  3. this() 在构造器中调用构造器

    只能在 构造器中必须 在第一句,能且 只能 调用一次

  4. ClassName.this 来引用外部类

    适用于 外部类的成员变量或者方法 和 内部类 重名的时候
    • ClassName.this 用于锁定外部类对象,进而可以调用外部类的成员变量和方法
    • test()this.test() 都会调用当前对象的方法,即内部类对象,所以都是调用了内部类的成员变量和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// java test

public class This

class Inner {
private void innerTest() {
this.test();
test();
This.this.test();
int i = 1;
}
private void test() {
System.out.println("inner class test");
}
}
private void test() {
System.out.println("out class test");
}

private static void classNameThis() {
Inner inner = new This().new Inner();
inner.innerTest();
/* Output:

inner class test
inner class test
out class test
*///:~
}

}

static 关键字

  1. 使用了 static 修饰的变量或者方法会独立出来,不会与包含它的类的任何实例对象关联再一起。
  2. static 修饰的 方法属性,可以通过 全类名.变量名/方法名 直接调用

静态和非静态的关系

  • 静态方法中不能直接调用非静态的属性属性/方法只能通过对象实例的引用 来调用 拥有权限的属性/方法
  • 非静态的方法中可以直接调用静态的方法

关于 static 和 内部类 的关系会放到后面来分析。


例如下面的测试中,value 属性独立了出来,不和任何一个实例对象相关联,所有的 Static 公用这个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// java test

public class Static {

private static int value = 0;

private Static() {}

private Static(int value) {
this.value = value;
}

@Override
public String toString() {
return ".value: " + Static.value;
}

private static void staticParam() {
Static s1 = new Static();
Static s2 = new Static();
s1.value++;

System.out.println("s1" + s1);
System.out.println("s2" + s2);

/* Output:

s1.value: 1
s2.value: 1
*///:~
}

// static void staticParam() 方法为静态方法不需要通过对象实例,可以直接调用
public static void main(String[] args) {
staticParam();
}

}

final 关键字

final 数据

  1. 适用情况
    • 永远不发生改变的常量
    • 运行时被初始化的值,而不希望被改变
  1. final 修饰的变量同样可以先声明,再初始化
    • 成员变量必须在
  1. staticfinal 共同修改的域占据一段 不可修改 的存储空间

  2. final 同样可以用来修饰 形参,这个类似于 c++ 中的 const 关键字,都是锁定常量为 只读,无法进行修改

  3. 不管是 基本类型 还是 包装类型,使用了 final 关键字修饰以后都 不能进行修改操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// java test

public class Final {

private final int i = 3;
private final Integer integer = 2;
private final String str = "";

private final int initVal;

private Final() {
//! init(); // 必须直接在构造器中完成初始化
initVal = 1;
}

/*
在构造器中,调用初 init() 始化方法中实现初始化是不可行的! x
*/
private void init() {
//! this.initVal = 2;
}

private static void finalData() {
Final _final = new Final();
//! _final.integer = 4;
//! _final.integer += 4;
//! _final.i = 5;
//! _final.i += 5;
//! _final.str = "2";
//! _final.str += "2";
}

}

final 方法

防止方法覆盖(重写)final 关键字将方法锁定,其导出类中无法对该方法进行重写操作
private 关键字修饰方法都是隐式地指定为了 final,无法被重写,给 private 修改的方法添加 final 关键字的是多余的

final 类

禁止继承,不会有任何子类,其内部属性都别隐式地指定为了 final 变量