1.变量及运算符
基本概念
- 所有东西都要存在内存才能进一步被使用(请不要抬杠😔)
- 一般地,这些东西要起名才方便使用
- 更一般的,名字不能重名,否则将导致歧义
- 进一步的,有些名字不能起,因为前人已经占用了
- 内存是宝贵的资源,从计算机诞生到内存白菜价的今天依然适用
- 所以放在内存里的东西得提前确定好大小以便分配内存空间,系统不会「看心情」多分配
- Java 已有预定义的基本类型来指定一个东西占的内存大小
由以上可知,要使用一个东西,起码要考虑两个方面:①类型②名字
变量类型
Java
是静态强类型的语言,这意味着,在Java
的世界里,所有东西都得有确定的类型,并且一经指定,类型再不可更改。比方说你有5个苹果,我可以使用预定义类型int
来指定存储的这个 5 的类型或者说指定这个 5 占多大内存;又或者描述圆周率 π,我可以使用预定义类型double
来指定存储的这个 3.14159... 的类型或者说指定其占多大内存。
有些其他语言可能是动态类型或者是弱类型,以下附图(看看即可)
更完整的类型系统对比表格参见(不看亦可):
https://en.wikipedia.org/wiki/Comparison_of_programming_languages_by_type_system
以下是Java
预定义的8个基本类型
分类 | 名字及内存占用 |
---|---|
整数 |
byte -1字节;short -2字节;int -4字节;long -8字节 |
浮点数 |
float -4字节;double -8字节 |
字符 |
char -JVM 内部占用2字节 |
布尔类型 |
boolean -不探讨占内存大小 |
最常用的是int
、double
、char
、boolean
。另外如果直接把 10、20 这样的字面量常量写在程序里,会认为这是 int
类型,如果直接写 3.14 会认为这是double
。
另外上面提到了「JVM」,这是指「Java 虚拟机」,不展开讲,简单地说 Java 程序无法直接「感知」到物理计算机,因为其实际运行在 JVM 虚拟出的环境中。可以这么认为 JVM 就是 Java 程序的运行环境,Java 程序认为 JVM 就是「真实」的计算机。打个比方,《黑客帝国》中人们生活在 Matrix 虚拟出的世界中,这里的 JVM 可以类比是 Matrix,Java 程序是生活在里面的人。后面再提到的「计算机」、「运行环境」都是指 JVM,不再复述。
名字
如果直接使用 10、20、3.14 这样的数不是不行,但是如果多个地方都要引用该数值,写的就太麻烦了。我们可以起个名字来代指这个数值。如果可以多次更改其值就叫变量
,如果赋值一次后再不可更改叫常量
,都是字面意思,不解释了。现在先探讨变量,常量到 Java OOP 提到final
再说。
Java
标识符命名是有规则:
- 名字必须由字母、数字、下划线 _、美元符号 $ 组成
- 不能以数字开头
- 不能和已有变量名重名
- 不能和特定词即关键字(key word)重名
把握以上 4 点就不会错了。比如以下命名是合法的:abc、ABC123、a_123、_abc、$abc、苹果,而这些是不合法的:2abc、abc#456、xyz abc。最后一个不合法是因为中间有空格。
提一下,这里所称的字母并不单单指英文字母,也包括其他国家语言文字的字符,比如上面的合法示例「苹果」,这正是Java
从诞生起就是原生支持多国语言文字特性的优势(😰虽然采用 Unicode-16 有大坑,不再详述)。
但是!推荐起名的做法是
- 始终以英文字母开头
- 遵守小驼峰命名法
小驼峰命名法是指如果名字是由多个单词组成的,那么第一个单词首字母小写,从第二个单词起,每个单词的首字母都大写。比如这些:maxGrade、minSalary、writeObject。本教程将始终坚持小驼峰命名法,后不再复述,直到类名采用大驼峰命名法时再提命名规范。
变量声明和使用
上面提到了变量,Java
中声明一个变量是简单的,比如下面这样:
int apple = 5;
这是声明了一个int
类型的变量,叫 apple,并立即初始化为 5,它占内存 4 字节。所有变量的声明语法都是一样的,先是变量类型,再是变量名字。再看几个例子:
double pi = 3.14159;
char letterA = 'a';
char letterB = '中';
boolean b1 = true;
boolean b2 = false;
其中字符类型的值必须用两个「单引号」包裹住,布尔类型只有两个合法值 true 和 false(🧐虽然 JVM 底层就把 boolean 当成 int,不展开谈)。留意到上述字符类型的值可以为汉字,这也是 Java
原生支持多国语言文字的便利。
我们再看看其他的,我们可以对变量做些简单运算,比如
int apple = 5;
apple = apple + 3;
上述代码的意思是把 apple 这个变量的原值 5 拿出来和 3 做加法,然后把结果再赋值给 apple,即是 8。要指出的是第 2 行代码不要看成是数学里面的概念,不能改成如下形式
apple - apple = 3 ==》0 = 3
这也许是初学者一开始不理解的地方,0 怎么会等于 3 了。我要指出的是上述代码不是数学里面的表达式,不能把变量在等号左右变来变去。编程里面,等号的意思是赋值,先计算右边的结果,再赋值给左边。
更多的运算符
可以先看看最常见的「五则运算」
int num = 1 + 2 - 3 * 5 / 5 % 6;
其中加减不必谈,* 是乘法的意思,不能用 X 代替,/ 是除法的意思,不能用 ÷ 代替(你也敲不出这个符号😂),% 是数学里取余(求模)的操作,比如 10÷3=3······1
,则10%3
的结果就是 1。求模在计算机科学里有重要的作用,这里不展开。按照先乘除后加减的顺序(优先级,后面详细介绍)计算,num 的结果是 0。
算术运算符
顾名思义就是做计算的,一共有 5 个。
意义 | 符号 |
---|---|
加法 | + |
减法 | - |
乘法 | * |
除法或整除 | / |
取余 | % |
除了/
外,别的运算符很简单,用法已经在上面提到了。而/
在不同计算里有不同的含义。
- 如果左右操作数都是
int
,那就代表整除。结果是做数学除法后直接舍掉(不是四舍五入)小数部分,比如6/2
的结果是 int 类型的 3,而10/3
的结果也是 int 类型的 3。 - 如果左右操作数有一个是浮点数类型,那就是普通的除法,是数学上的结果,有小数位,比如
10.0/3
的结果就是 double 类型的 3.33 (具体精度后面再说)
关系运算符
数学上有大于、等于、小于的比较运算符,Java
也提供了这些对应的运算符。
意义 | 符号 |
---|---|
大于、大于等于 | >、>= |
是否相等 | == |
小于、小于等于 | <、<= |
不等于 | != |
注意,大于等于不能用数学上的符号 ≥(你也敲不出这个符号😂),小于等于、不等于同理。另外就是判断两个变量是否相等要用连续两个 =,而不是一个 =,请一定要注意这一点,这是初学者最容易犯的错误之一。
关系运算符运算的结果是true
或者false
。
逻辑运算符
数学上有「与」、「或」、「非」的概念,Java
也提供了对应运算符。
意义 | 符号 |
---|---|
逻辑与 | && |
逻辑或 | || |
逻辑非 | ! |
逻辑运算的规则不再繁述。
自增、自减运算符
和 C语言一样,Java
也提供了这个快捷的操作。在程序中对变量递增 1 或递减 1 是很常见的运算,此时可以这样写:
int i = 0;
i++; //现在 i 的值是 1
++i; //现在 i 的值是 2
而不用这样写 i = i + 1
。上面看到了前缀形式和后缀形式,它们是有区别的。再看例子:
int m = 1,n = 1;
int j = m++; //j 的值是 1,m 变成 2
int k = ++n; //k 的值是 2,n 变成 2
该怎么理解上述代码呢?其实前缀++会先把 n 做自增 1,变成 2,然后把 2 赋值给 k,所以 k 和 n 都是 2;而后缀++会先把 m 的原值赋值给 j,然后 m 再自增 1。如果对代码做反编译会看得更明白,但是不展开。自减是同理,不再举例子。
一般来说前缀形式不会有歧义,先改变变量值再参与运算,最让人迷惑的是后缀。比如:
int a = 5, b = 6;
int c = a++ + b++; // 执行完后 a 的值是 6,b 的值是 7,c 的值是 11
为什么变量 c 是 11?和上面的分析一样,把 a、b的原值拿出来做加法,所以是11,然后 a、b的各自自增 1。但是!实际中不要在一个表达式不要写出两个及以上后缀,就像上面那样,也不要混用前缀和后缀,比如:
int a = 5;
int c = ++a + a++; //有结果,但是这样写是不符合工程规范的,这是考验人的眼睛
还有变量后缀++然后赋值给自己的:
int a = 5;
a = a++; //有结果但不是想的那个结果,这就牵扯到别的问题了
实际中,要么使用i++
单独一行的形式,要么最多允许这样a = b + c++
,更复杂的形式就不该写了。有前辈这样说
代码是给人看的,只是偶尔在机器上运行
写代码是为了解决问题,不是为了炫技,写正确、简洁的代码不要写有歧义的代码。
复合赋值运算符
有时候为了简洁,把诸如a = a + 3
这样的形式写成a += 3
,+=
是复合运算符,在单独写一行不跟别的运算符混合时和前者写法等价,实际中不要写出a+=3+1
这样的写法,只允许单独用不可以和别的运算符混合,原因和后缀++一样,为了代码一目了然。「五则运算」都拥有复合形式。
运算符 | 范例 | 结果 |
---|---|---|
+= | a += b | a = a + b |
-= | a -= b | a = a - b |
*= | a *= b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
类型转换
前面说过,Java
是很看重类型。事实上,一般地,不同类型的数据是不能做运算的,比如:
long a = 3;
int b = a; // 代码错误
第 2 行 b 是int
类型,a 是long
类型,long
占的空间更大,可存储的数字更大,它是不能赋值给较小空间,可存储数字较小的int
变量的。但是第 1 行 3 是int
类型,怎么就可以赋值给long
的变量呢?因为Java
允许自动类型转换(也称隐式类型转换),简单说就是不同的类型的数据做运算,运算时以长度最长的类型为主,其他数值自动提升至最长的类型(如上述代码第 1 行),但是反过来不行(如第 2 行)。如果确实类型要向下转换,那得使用专门的语法——强制类型转换(也称显式类型转换),如下所示的圆括号:
double a = 3.14;
int b = (int)a; //b 的值是 3,直接舍掉小数位
一般地,少用强制类型转换,也不要滥用隐式类型转换,最好始终坚持同类型的数据做运算。
运算优先级
运算符优先级有着严格的规定,一个表达式执行顺序是先按运算符优先级,高的(数字越小越高)先执行,同样优先级的,再按结合性的顺序执行。以下见表(看看即可):
优先级 | 运算符 | 简介 | 结合性 |
---|---|---|---|
1 |
[ ] 、. 、( )
|
方法调用,属性获取 | 从左向右 |
2 | !、~、 ++、 -- | 一元运算符 | 从右向左 |
3 | * 、/ 、% | 乘、除、取模(余数) | 从左向右 |
4 | + 、 - | 加减法 | 从左向右 |
5 | <<、 >>、 >>> | 左位移、右位移、无符号右移 | 从左向右 |
6 | < 、<= 、>、 >=、 instanceof | 小于、小于等于、大于、大于等于, 对象类型判断是否属于同类型 | 从左向右 |
7 | == 、!= | 2个值是否相等,2个值是否不等于。 下面有详细的解释 | 从左向右 |
8 | & | 按位与 | 从左向右 |
9 | ^ | 按位异或 | 从左向右 |
10 | | | 按位或 | 从左向右 |
11 | && | 短路与 | 从左向右 |
12 | || | 短路或 | 从左向右 |
13 | ?: | 条件运算符 | 从右向左 |
14 | =、 += 、-= 、*= 、/=、 %=、 &=、 |=、 ^=、 <、<= 、>、>= 、>>= | 混合赋值运算符 | 从右向左 |
事实上,不用全部记住这个表格,只需要记住①先乘除后加减;②自增自减比乘除还高;③括号优先级更高,当不清楚先运算那个时就打括号以明确。
其他
- 要知道浮点数:
double
,float
存储可能不准确,从而导致算术运算结果也不准确,一定不能用==
来判断两个浮点数是否相同。这不是Java
的错,原因不展开,有兴趣可以自行搜索。如果要精确的小数计算,可以使用BigDecimal
类来解决,在后面的常见 Java 类使用中再予以介绍。 - ASCII码。应该予以了解,但篇幅所限,这里不展开,网上有太多的文章。但要记住小写字母 a,大写字母 A 和字符 0 的 ASCII 码,这是 IT 的常识。
还有什么没有谈到
以下内容,有时间或学完了回头再看看,正常学习时可以跳过。
- 浮点数内部存储结构
- 浮点数为什么不准确
- 更多的逻辑运算符
- 位运算及二进制
- 逻辑运算规则
- 逻辑运算存在的「短路」现象
- 反编译看前缀++和后缀++
- 数字字面量常量后缀 F 和 L
- 不同整数类型能表示的最大值是规定的还是计算出来的