【Java】变量、基本数据类型、运算符

变量、基本数据类型、运算符
关键字
被Java语言赋予了特殊含义,用做专门用途的字符串(或单词)。关键字都是小写字母。
官方地址: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
其中const和goto是保留字;而true,false,null不在其中,它们看起来像关键字,其实是字面量,表示特殊的布尔值和空值。
标识符
Java中变量、方法、类等要素命名时使用的字符序列,称为标识符。
命名规则
- 标识符可以由大小写字母、数字、下划线(_)和美元符号($)组成,但是不能以数字开头,不能有空格。
- 变量不能重复定义,大小写敏感,长度无限制。
- 不可以使用关键字和保留字,但能包含关键字和保留字。
命名规范
- 使用有意义的名称,达到见名知意。
- 包名:
- 类名、接口名:
- 变量名、方法名:
- 常量名:所有字母大写,多个单词时每个单词用下划线连接。例如:MAX_VALUE
数据类型简介
Java中变量的数据类型分为两大类:
-
基本数据类型:包括
整数类型(byte、short、int、long)、浮点数类型(float、double)、字符类型(char)、布尔类型(boolean)。 -
引用数据类型:包括
数组、类、接口、枚举、注解、记录。
变量与基本数据类型
变量
变量的概念:
-
内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
-
变量的构成包含三个要素:
数据类型、变量名、存储的值 -
Java中变量声明的格式:
数据类型 变量名 = 变量值
变量的作用:用于在内存中保存数据。
使用变量注意:
- Java中每个变量必须先声明,后使用。
- 使用变量名来访问这块区域的数据。
- 变量的作用域:其定义所在的一对{ }内。
- 变量只有在其
作用域内才有效。出了作用域,变量不可以再被调用。 - 同一个作用域内,不能定义重名的变量。
变量的数据类型可以是基本数据类型,也可以是引用数据类型;
变量赋值,就是把“值”存到该变量代表的内存空间中,给变量赋的值类型必须与变量声明的类型一致或兼容。
// 举例
int a;int b = 10;
b = 20;int c;
c = 11;int m, n;int d = 1;
int e = d;int x = 1;
int y = 2;
int z = 2 * x + y;int aa = b = 0; // aa = 0, b = 0
基本数据类型
计算机中的二进制表示🌠
在计算机中,所有的内容都是二进制(补码)形式表示。十进制是以10为进位,如9+1=10;二进制则是满2进位(因为计算机是电子的,电平信号只有高位和低位,可以暂且理解为通电和不通电,高电平代表1,低电平代表0,由于只有0和1,因此只能使用2进制表示我们的数字!)比如1+1=10=2^1+0,一个位也叫一个bit,8个bit称为1字节,16个bit称为一个字,32个bit称为一个双字,64个bit称为一个四字,一般采用字节来描述数据大小。
111 = 7
注意小b代表的是bit,大B代表的是Byte字节(8bit = 1Byte字节),所以说办理宽带的时候,100Mbps这里的b是小写的,所以说实际的网速就是100/8 = 12.5 MB/s了。
十进制的7 -> 在二进制中为 111 = 2^2 + 2^1 + 2^0
现在有4个bit位,最大能够表示多大的数字呢?
- 最小:0000 => 0
- 最大:1111 => 23+22+21+20 => 8 + 4 + 2 + 1 = 15
在Java中,无论是小数还是整数,他们都要带有符号(C语言有无符号数)所以,首位就作为我们的符号位,还是以4个bit为例,首位现在作为符号位(1代表负数,0代表正数):
- 最小:1111 => -(2^2 + 2^1 + 2^0) => -7
- 最大:0111 => +(2^2 + 2^1 + 2^0) => +7 => 7
现在,我们4bit能够表示的范围变为了-7~+7,这样的表示方式称为原码。虽然原码表示简单,但是原码在做加减法的时候,很麻烦!以4bit位为例:
1+(-1) = 0001 + 1001 = 怎么让计算机去计算?
于是引入了反码:
- 正数的反码是其原码本身
- 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反
经过上面的定义,再来进行加减法:
1+(-1) = 0001 + 1110 = 1111 => -0
思考:1111代表-0,0000代表+0,在实数的范围内,0有正负之分吗?0既不是正数也不是负数,那么显然这样的表示依然不够合理!根据上面的问题,引入了补码,定义如下:
- 正数的补码是其原码本身
- 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1(即在反码的基础上+1)
- 对补码再求一次补码就可得该补码对应的原码。
比如-7原码为1111,反码为1000,补码就是1001了,-6原码为1110,反码为1001,补码就是1010。所以在补码下,原本的1000就作为新增的最小值-8存在。
所以现在就已经能够想通,-0已经被消除了!我们再来看上面的运算:
1+(-1) = 0001 + 1111 = (1)0000 => +0 (现在无论怎么算,也不会有-0了!)
所以现在,1111代表的不再是-0,而是-1,相应的,由于消除-0,负数多出来一个可以表示的数(1000拿去表示-8了),那么此时4bit位能够表示的范围是:-8~+7(Java使用的就是补码!)
进制转换
-
十进制(decimal)
- 数字组成:0-9
- 进位规则:满十进一
-
二进制(binary)
- 数字组成:0-1
- 进位规则:满二进一,以
0b或0B开头
-
八进制(octal):很少使用
- 数字组成:0-7
- 进位规则:满八进一,以数字
0开头表示
-
十六进制
- 数字组成:0-9,a-f
- 进位规则:满十六进一,以
0x或0X开头表示。此处的 a-f 不区分大小写
int num1 = 123; //十进制
int num2 = 0b101; //二进制
int num3 = 0127; //八进制
int num4 = 0x12aF; //十六进制
略
整数类型:byte、short、int、long
Java各整数类型有固定的表数范围和字段长度,不受具体操作系统的影响,以保证Java程序的可移植性。
- byte 字节型 (8bit,1字节)范围:-128~+127
- short 短整形(16bit,2字节)范围:-215~+215-1
- int 整形(32bit,4字节)范围:-231~+231-1 (默认)
- long 长整形(64bit,8字节)范围:-263~+263-1
[!NOTE]
计算机存储单位
**字节(Byte):**是计算机用于
计量存储容量的基本单位,一个字节等于8 bit。**位(bit):**是数据存储的
最小单位。二进制数系统中,每个0或1就是一个位,叫做bit(比特),其中8 bit 就称为一个字节(Byte)。转换关系:
- 8 bit = 1 Byte
- 1024 Byte = 1 KB
- 1024 KB = 1 MB
- 1024 MB = 1 GB
- 1024 GB = 1 TB
浮点类型:float、double
与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。
- float 单精度浮点型 (32bit,4字节)
- double 双精度浮点型(64bit,8字节) (默认)
那么小数在计算机中又是如何存放的呢?
float 单精度实数的存储结构:
S(31) E(阶码,30~23,共8位) M(尾数,22~0,共23位)
double 双精度实数的存储结构:
S(63) E(阶码,52~62,共11位) M(尾数,51~0,共52位)
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:
V=(−1)S×M×2E
V = (-1)^S \times M \times 2^E
V=(−1)S×M×2E
- (−1)S(-1)^S(−1)S 表示符号位,当 S=0,V 为正数;当 S=1,V 为负数。
- M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。(例如尾数为1111010,那么M实际上就是1.111010,尾数首位必须是1,1后面紧跟小数点,如果出现0001111这样的情况,去掉前面的0,移动1到首位;题外话:随着时间的发展,IEEE 754标准默认第一位为1,故为了能够存放更多数据,就舍去了第一位,比如保存1.0101 的时候, 只保存 0101,这样能够多存储一位数据)
- 2E2^E2E 表示指数位(用于移动小数点,所以说才称为浮点型),指数默认存储需要添加127的偏移量,便于同时处理正负指数的情况。
比如, 对于十进制的 5.25 对应的二进制为:101.01,相当于:1.0101×221.0101 \times 2^21.0101×22。所以,S 为 0,M 为 0101,E 为 2。因此,对于浮点类型,最大值和最小值不仅取决于符号和尾数,还有它的阶码,所以浮点类型的大致取值范围:
- 单精度:±3.40282347×1038±3.40282347 \times 10^{38}±3.40282347×1038
- 双精度:±1.79769313486231570×10308±1.79769313486231570 \times 10^{308}±1.79769313486231570×10308
浮点型常量有两种表示形式:
- 十进制数形式。如:5.12 512.0f .512 (必须有小数点)
- 科学计数法形式。如:5.12e2 512E2 100E-2
定义float类型的变量,赋值时需要以"f"或"F"作为后缀。 ?????
[!WARNING]
关于浮点型精度的说明
并不是所有的小数都能可以精确的用二进制浮点数表示。二进制浮点数不能精确的表示0.1、0.01、0.001这样10的负次幂。
浮点类型float、double的数据不适合在
不容许舍入误差的金融计算领域。如果需要精确数字计算或保留指定位数的精度,需要使用BigDecimal类。举例
double a = 0.1; double b = 0.2; double c = a + b;System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("a + b = " + c); // a + b = 0.30000000000000004 System.out.println("a + b == 0.3 ? " + (c == 0.3)); // a + b == 0.3 ? falseBigDecimal a = new BigDecimal("0.1"); BigDecimal b = new BigDecimal("0.2"); BigDecimal c = a.add(b); System.out.println("a + b = " + c); // a + b = 0.3
字符类型:char
char 型数据用来表示通常意义上“字符”(16个bit,2字节,不带符号,范围是0 ~ 65535)
Java中的所有字符都使用Unicode编码
字符型变量的三种表现形式:
-
**形式1:**使用单引号(’ ')括起来的
单个字符。例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’;
-
**形式2:**直接使用
Unicode值来表示字符型常量:‘\uXXXX’。其中,XXXX代表一个十六进制整数。例如:\u0023 表示 ‘#’。char c = ‘\u0023’;
-
**形式3:**允许使用
转义字符'\'来将其后的字符转变为特殊字符型常量。例如:char c3 = ‘\n’; // '\n’表示换行符
| 转义字符 | 说明 | Unicode表示方式 |
|---|---|---|
\n | 换行符 | \u000a |
\t | 制表符 | \u0009 |
\" | 双引号 | \u0022 |
\' | 单引号 | \u0027 |
\\ | 反斜线 | \u005c |
\b | 退格符 | \u0008 |
\r | 回车符 | \u000d |
字符编码
比如英文字母A其对应的ASCII码值为65
char c = 'A'
// 等价
char c = 65;
System.out.println(c); // A
字符表里面就128个字符,那char干嘛要两个字节的空间来存放呢?表中的字符只包含了一些基础的字符,而那么多中文字符,用ASCII编码表那128个肯定没办法全部表示,这时,就需要扩展字符集了。
可以使用两个甚至多个字节来表示一个中文字符,这样我们能够表示的数量就大大增加了,GB2132方案规定当连续出现两个大于127的字节时(注意不考虑符号位,此时相当于是第一个bit位一直为1了),表示这是一个中文字符(所以为什么常常有人说一个英文字符占一字节,一个中文字符占两个字节),这样我们就可以表示出超过7000种字符了,不仅仅是中文,甚至中文标点、数学符号等,都可以被正确的表示出来。
不过这样能够表示的内容还是不太够,除了那些常见的汉字之外,还有很多的生僻字,比如龘、錕、釿、拷这类的汉字,后来干脆直接只要第一个字节大于127,就表示这是一个汉字的开始,无论下一个字节是什么内容(甚至原来的128个字符也被编到新的表中),这就是Windows至今一直在使用的默认GBK编码格式。
虽然这种编码方式能够很好的解决中文无法表示的问题,但是由于全球还有很多很多的国家以及很多很多种语言,所以我们的最终目标是能够创造一种可以表示全球所有字符的编码方式,整个世界都使用同一种编码格式,这样就可以同时表示全球的语言了。所以这时就出现了一个叫做ISO的(国际标准化组织)组织,来定义一套编码方案来解决所有国家的编码问题,这个新的编码方案就叫做Unicode(准确的说应该是规定的字符集,包含了几乎全世界所有语言的字符),规定每个字符必须使用两个字节,即用16个bit位来表示所有的字符(也就是说原来的那128个字符也要强行用两位来表示)
但是这样的话实际上是很浪费资源的,因为这样很多字符都不会用到两字节来保存,肯定不能直接就这样去表示,这会导致某些字符浪费了很多空间,我们需要一个更加好用的具体的字符编码方式。所以最后就有了UTF-8编码格式(它是Unicode字符集的一个编码规则),区分每个字符的开始是根据字符的高位字节来区分的,比如用一个字节表示的字符,第一个字节高位以“0”开头;用两个字节表示的字符,第一个字节的高位为以“110”开头,后面一个字节以“10开头”;用三个字节表示的字符,第一个字节以“1110”开头,后面俩字节以“10”开头;用四个字节表示的字符,第一个字节以“11110”开头,后面的三个字节以“10”开头。
所以如果我们的程序需要表示多种语言,最好采用UTF-8编码格式,它最多可以表示4个字节的内容。随着编码规则的演进,后来又有了UTF-16编码格式,采用BMP字符节省空间,它大量对于中文字符的应用效果更好,Java在运行时采用的就是UTF-16,几乎全世界的语言用到的字符都可以表示出来。
| Unicode符号范围(十六进制) | UTF-8编码方式(二进制) |
|---|---|
| 0000 0000 ~ 0000 007F | 0xxxxxxx |
| 0000 0080 ~ 0000 07FF | 110xxxxx 10xxxxxx |
| 0000 0800 ~ 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 0001 0000 ~ 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
注意: Unicode 是“字符集”,也就是有哪些字符,而UTF-8、UTF-16 是“编码规则”,也就是怎么对这些字符编码,怎么以二进制的形式保存。
char 类型是可以进行运算的。因为都对应有Unicode码,可以看做是一个数值。
int a = '淦';
System.out.println(a); // 28314int 你干嘛 = 666; // 甚至可以变量名写成中文char c = '啊这'; // 编译错误char c = 'A'; // 字符 'A' 的 Unicode 值是 65
int result = c + 1;
System.out.println("字符 c: " + c);
System.out.println("c + 1 的整数值: " + result); // 66
System.out.println("对应的字符: " + (char) result); // Bchar c1 = 'C';
char c2 = 'A';
int diff = c1 - c2; // 2char c = '1'; // '1' 的 Unicode 值是 49
int num = 1;
System.out.println(c + num); // 50
System.out.println("" + c + num); // 11
布尔类型:boolean
布尔类型(boolean)只有true和false两种值;😊不同于C语言,不可以使用 0 或非 0 的整数替代 false 和 true;
通常用于流程控制判断语句;
布尔类型占据的空间大小并未明确定义,而是根据不同的JVM会有不同的实现;
拓展:Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达所操作的boolean值,在编译之后都使用java虚拟机中的int数据类型来代替:true用1表示,false用0表示。——《java虚拟机规范 8版》
boolean b = true;
boolean a = 6; // 编译错误if (!b) {...
} else {...
}
溢出问题
int a = 2147483647; //int最大值
a = a + 1; // 继续加// a = -2147483648
当 int 为最大值时,二进制表示形式为:
- 2147483647 = 01111111 11111111 11111111 11111111(第一个是符号位0,其他的全部为1,就是正数的最大值)
那么此时如果加1,会进位成:
- 10000000 00000000 00000000 00000000
符号位为1,那么此时表示的不就是一个负数了吗?这就是补码形式下的最小值
所以说最后的结果就是int类型的最小值:-2147483648。
基本数据类型变量间运算规则
在Java程序中,不同的基本数据类型(只有7种,不包含boolean类型)变量的值经常需要进行相互转换。
转换的方式有:
自动类型提升(隐式类型转换)
规则:将取值范围小(或容量小)的类型自动提升为取值范围大(或容量大)的类型 。
byte char short -> int -> long -> float -> double
(1)当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时
short a = 10; // ✅
int b = a; // 小的类型可以直接传递给表示范围更大的类型int a = 10;
short b = a; // ❌编译错误byte b = 10; // ✅虽然 10 默认是 int,但是这里有一个编译期常量优化规则(常量折叠):如果一个 int 字面量的值 在目标类型的取值范围内,并且是 编译期常量,Java 编译器允许自动进行类型转换(称为常量赋值转换)。
short s = 100;
byte b = 128; // ❌编译错误
//非编译期常量,不会自动转换
int x = 10;
byte b = x; // ❌编译错误:需要显式强制类型转换int i = 'A'; // char自动升级为int,其实就是把字符的编码值赋值给变量i
double d = 10; // int自动升级为double
long num = 1234567; // 1234567在int范围内
long a = 922337203685477580; // ❌编译错误 按照`long`类型的规定实际上是可以表示这么大的数字的,因为直接在代码中写的常量数字,默认情况下就是`int`类型,`int`表示不下
long a = 922337203685477580L; // 右边的整数常量值如果超过int范围,必须加l或L,显式表示long类型。否则编译错误。(默认int)// 针对于这种很长的数字,为了提升辨识度,可以使用下划线分割每一位
int a = 1_000_000; // 表示1000000byte bigB = 130; // ❌编译错误 130超过byte范围float a = 9.9; // ❌编译错误 小数类型常量默认都是`double`类型
float a = 9.9F; // ✅添加`f`或`F`来表示这是一个`float`类型的常量值float f = 9.9F;
double a = f; // 隐式类型转换为doublelong l = 21731371236768L;
float f = l; // 这里能编译通过吗?
/*
`long`类型的值居然可以直接丢给`float`类型隐式类型转换,很明显`float`只有32个bit位,而`long`有足足64个,这是什么情况?怎么大的还可以隐式转换为小的?这是因为虽然`float`空间没有那么大,但是由于是浮点类型,指数可以变化,最大的数值表示范围实际上是大于`long`类型的,虽然会丢失精度,但是确实可以表示这么大的数。
*/
short s = 5;
s = s - 2; // ❌编译错误 s - 2 被提升为 intbyte b = 3;
b = b + 4; // ❌编译错误 b + 4 被提升为 int
b = (byte)(b + 4);char c = 'a';
int i = 5;
float d = .314F;
double result = c + i + d;byte b = 5;
short s = 3;
short t = s + b; // ❌编译错误 s + b 被提升为 int
(2)当存储范围小的数据类型与存储范围大的数据类型变量一起混合运算时,会按照其中最大的类型运算。
int i = 1;
byte b = 1;
double d = 1.0;double sum = i + b + d; // 混合运算,升级为double
(3)当 byte, short, char 数据类型的变量进行算术运算时,按照 int 类型处理。
byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2; // ❌编译错误,b1 + b2自动升级为intchar c1 = '0';
char c2 = 'A';
int i = c1 + c2; // 至少需要使用int类型来接收
System.out.println(c1 + c2); // 113
强制类型转换
规则:将取值范围大(或容量大)的类型强制转换成取值范围小(或容量小)的类型。
自动类型提升是Java自动执行的,而强制类型转换是自动类型提升的逆运算,需要手动执行。
转换格式:
数据类型1 变量名 = (数据类型1) 被强转数据值; //()中的数据类型必须<=变量值的数据类型
(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)强制转换为存储范围小的变量时,可能会损失精度或溢出。
int i = 3.14; // ❌编译错误
int i = (int) 3.14; // 损失精度double d = 1.2;
int num = (int) d; // 损失精度int i = 200;
byte b = (byte) i; // 溢出 b = -56 byte只有一个字节,只保留最后8位,第一个位置是符号位float f1 = 12.3; // ❌编译错误
float f3 = (float) 12.3;
(2)当某个值想要提升数据类型时,也可以使用强制类型转换。这种情况的强制类型转换是没有风险的,通常省略。
int i = 1;
int j = 2;
double bigger = (double) (i / j); // bigger = 0.0
运算符
运算符的分类:
- 按照
功能分为:算术运算符、赋值运算符、比较(或关系)运算符、逻辑运算符、位运算符、条件运算符、Lambda运算符
| 分类 | 运算符 |
|---|---|
| 算术运算符(7个) | +、-、*、/、%、++、– |
| 赋值运算符(12个) | =、+=、-=、*=、/=、%=、>>=、<<=、>>>=、&=、|=、^=等 |
| 比较(关系)运算符(6个) | >、>=、<、<=、==、!= |
| 逻辑运算符(6个) | &、|、^、!、&&、|| |
| 位运算符(7个) | &、|、^、~、<<、>>、>>> |
| 条件(三元)运算符(1个) | (条件表达式)?结果1:结果2 |
| Lambda运算符(1个) | -> |
- 按照
操作数个数分为:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目运算符)
| 分类 | 运算符 |
|---|---|
| 一元运算符(单目运算符) | 正号(+)、负号(-)、++、–、!、~ |
| 二元运算符(双目运算符) | 除了一元和三元运算符剩下的都是二元运算符 |
| 三元运算符 (三目运算符) | (条件表达式)?结果1:结果2 |
算术运算符
1、+:正号、加法、字符串拼接
int a = 1 + 1;
变量可以参与到算术运算中
int a = 3;
int b = a + 10;
不同类型之间也可以进行运算
int a = 5;
short b = 10;
short c = a + b; // ❌编译错误
int c = a + b;
// 使用整数进行运算,小类型需要转换为大类型,short、byte、char一律转换为int再进行计算(无论算式中有无int,都会自动转换)结果也是int;如果算式中出现了long类型,那么全部都需要转换到long类型再进行计算,结果也是long。int a = 5;
float b = 10;
int c = a + b; // ❌编译错误
作为正号似乎没什么用
int a = -1;
int b = +a; // b = -1
支持对字符串、基本数据类型的拼接
String str = "伞兵" + "lbw"; // 伞兵lbw
String str = "伞兵" + true + 1.5 + 'A'; // 伞兵true1.5A
2、-:负号、减法
int a = 3;
int b = 4;
int c = a - b; // -1
负号,相反数
int a = -1;
int b = -a; // 1
3、*、\ 乘法除法
int a = 8, b = 2;
System.out.println(a * b); // 16
System.out.println(a / b); // 4
两个整数进行除法运算时,得到的结果也是整数(直接砍掉小数部分,不是四舍五入)
int a = 8, b = 5;
System.out.println(a / b); // 1// 使用强制类型转换
System.out.println(a / (double) b); // 1.6
System.out.println((double) a / b); // 1.6
System.out.println((double) (a / b)); // 1.0
两个小数一起计算的话,结果是小数
double a = 8.0, b = 5.0;
System.out.println(a / b); // 1.6
整数和小数一起计算,结果是小数
double a = 8.0;
int b = 5;
System.out.println(a / b); // 1.6
4、%取模
即计算余数,结果与被模数符号相同
System.out.println(10 % 3); // 1System.out.println(5 % 2); // 1
System.out.println(5 % -2); // 1 5 除以 -2 = -2 余 1
System.out.println(-5 % 2); // -1 -5 除以 2 = -2 余 -1
System.out.println(-5 % -2); // -1
常用来判断奇偶
System.out.println(17 % 2); // 1 -> 17奇数
5、++、-- 自增自减
以 ++ 举例,--同理
// 等价
a = a + 1;
a++;
++a;
区别
- 变量
前++:变量先自增1,然后再运算。 - 变量
后++:变量先运算,然后再自增1。
int a = 8;
System.out.println(a++); // 8
// a = 9
System.out.println(++a); // 10
赋值运算符
-
符号:=
- 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换处理。
- 支持
连续赋值。 - 赋值运算符的左边必须是一个可以赋值的目标,比如变量。
-
扩展赋值运算符: +=、 -=、*=、 /=、%=
赋值运算符 符号解释 +=将符号 左边的值和右边的值进行相加操作,最后将结果赋值给左边的变量-=将符号 左边的值和右边的值进行相减操作,最后将结果赋值给左边的变量*=将符号 左边的值和右边的值进行相乘操作,最后将结果赋值给左边的变量/=将符号 左边的值和右边的值进行相除操作,最后将结果赋值给左边的变量%=将符号 左边的值和右边的值进行取余操作,最后将结果赋值给左边的变量
int i1 = 10;
int i2 = i1;
long l1 = i1; // 自动类型转换byte bb1 = (byte)i1; // 强制类型转换
使用连等可以将一连串变量都赋值为最右边的值
int a;
int b = a = 777;
当出现连续使用赋值运算符时,按照从右往左的顺序进行计算,首先是a = 777,计算完成后,a的值就变成了777,计算完成后,会得到计算结果(赋值运算的计算结果就是赋的值本身,就像1 + 1的结果是2一样,a = 1的结果就是1)此时继续进行赋值计算,那么b就被赋值为a = 777的计算结果,同样的也是 777 了。
// 举例说明 += -= *= /= %=
int m1 = 10;
m1 += 5; // 类似于 m1 = m1 + 5的操作,但不等同于
System.out.println(m1); // 15// ⭐ 拓展赋值的操作不会改变变量本身的数据类型
short s = 10;
s++;
++s;
s += 1; // 编译通过,因为在得到int类型的结果后,JVM自动完成强制类型转换,将int类型强转成short
s = s + 1; // ❌编译错误
s = (short)(s + 1);// 因此,推荐写法 += ++int i = 1;
i *= 0.1; // i = 0int m = 2;
int n = 3;
n *= m++; // m = 3 n = 6int n = 10;
n += (n++) + (++n); // n = n + (n++) + (++n) 32int a = 8;
int b = -a++ + ++a; // 先计算a++ 2
比较(关系)运算符
> 大于
< 小于
== 等于
!= 不等于
>= 大于等于
<= 小于等于
instanceof 检查是否是某个类的对象
- 比较运算符的结果都是
boolean类型,只有 true 和 false - > < >= <= :只适用于基本数据类型(除 boolean 类型之外)、== != :适用于基本数据类型和引用数据类型
- 😊比较运算符“
==”不能误写成“=”
逻辑运算符
逻辑运算符操作的都是boolean类型的变量或常量,而且运算得结果也是boolean类型的值。
& 和 && 与 当符号左右两边布尔值都是true时,结果才为true。否则,为false。
| 和 || 或 当符号左右两边布尔值有一边为true时,结果就为true。当两边都为false时,结果才为false。
! 非 当变量布尔值为true时,结果为false。当变量布尔值为false时,结果为true。
^ 异或 当符号左右两边布尔值不同时,结果为true。当两边布尔值相同时,结果为false。
逻辑运算符用于连接布尔型表达式,在 Java 中不可以写成 3 < x < 6,应该写成 x > 3 && x < 6 。
⭐区分& 和 &&、| 和 ||
&& 与 || 具有短路特性,只要符合左边能直接判断出表达式结果了,就不会执行符合右边操作
int a = 3, b = 4, c = 5;
System.out.println((a > b) & (a++ > c)); // a > b false a++ > c false
System.out.println(a); // 4System.out.println((a > b) && (a++ > c)); // a > b false
System.out.println(a); // 3System.out.println((a > b) | (a++ > c)); // a > b false a++ > c false
System.out.println("a = " + a); // 4System.out.println((a > b) || (a++ > c)); // a > b false a++ > c false
System.out.println("a = " + a); // 4System.out.println((a < b) || (a++ > c)); // a < b true
System.out.println("a = " + a); // 3
条件(三元)运算符
条件表达式 ? 结果1 : 结果2
// 条件表达式是boolean类型的结果,true 结果1,false 结果2
条件表达式是boolean类型的结果,根据boolean的值选择表达式1或表达式2
int a = 10;
char b = a > 10 ? 'A' : 'B'; // b = B
位运算符
位运算符的运算过程都是基于二进制的补码运算
【按位与&】让两个数每一位都进行比较,如果这一位两个数都是1,那么结果就是1,否则就是0
int a = 9, b = 3;
int c = a & b;
- a = 9 = 1001
- b = 3 = 0011
- c = 1 = 0001(因为只有最后一位,两个数都是1,所以说结果最后一位是1,其他都是0)
【按位或|】让两个数每一位都进行比较,如果这一位任意一个为1那么结果就是1:
int a = 9, b = 3;
int c = a | b;
- a = 9 = 1001
- b = 3 = 0011
- c =11= 1011(只要上下有一个是1或者都是1,那结果就是1)
【按位异或^】让两个数每一位都进行比较,如果这一位两边不相同,结果才是1:
int a = 9, b = 3;
int c = a ^ b;
- a = 9 = 1001
- b = 3 = 0011
- c =10= 1010(从左往右第二位、第四位要么两个都是0,要么两个都是1,所以说结果为0)
【按位取反~】这个数如果这一位上是1,变成0,如果是0,变成1:
byte c = ~127;
- 127 = 01111111
- -128 = 10000000
【左移<<】左边被移除的高位丢弃,右边空位补0
int c = 3 << 4; // 480000 0000 0000 0000 0000 0000 0000 0011 3
0000 0000 0000 0000 0000 0000 0011 0000 48int c = -3 << 4; // -481111 1111 1111 1111 1111 1111 1111 1101 -3
1111 1111 1111 1111 1111 1111 1101 0000 -48
在一定范围内,数据每向左移动一位,相当于原数据*2。(正数、负数都适用)
【右移>>】右边被移除的位丢弃,左边补当前符号位。
int c = 8 >> 2; // 20000 0000 0000 0000 0000 0000 0000 1000 8
0000 0000 0000 0000 0000 0000 0000 0010 2int c = -8 >> 2; // -21111 1111 1111 1111 1111 1111 1111 1000 -8
1111 1111 1111 1111 1111 1111 1111 1110 -2
在一定范围内,数据每向右移动一位,相当于原数据/2。(正数、负数都适用)
【无符号右移>>>】左边直接补0
int c = -1 >>> 11111 1111 1111 1111 1111 1111 1111 1111 -1
0111 1111 1111 1111 1111 1111 1111 1111 2147483647 // 正数的最大值
[!NOTE]
⭐不存在无符号左移 <<<
位移操作也可以缩写,如 <<=
运算符优先级
所谓优先级就是在表达式运算中的运算符顺序
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 | ( ) | 从左向右 |
| 2 | ! ~ - + (强制类型转换) ++ – | 从右向左 |
| 3 | * / % | 从左向右 |
| 4 | + - | 从左向右 |
| 5 | << >> >>> | 从左向右 |
| 6 | > < >= >= instanceof | 从左向右 |
| 7 | == != | 从左向右 |
| 8 | & | 从左向右 |
| 9 | ^ | 从左向右 |
| 10 | | | 从左向右 |
| 11 | && | 从左向右 |
| 12 | || | 从左向右 |
| 13 | ? : | 从右向左 |
| 14 | = += -= *= /= %= &= |= ^= <<= >>= >>>= | 从右向左 |
int a = 10;
int b = a = 8 * -a + 10;// -a = 10
// 8 * -10 = -80
// -80 + 10 = -70
// a = -70
// b = a = -70int b = (a = 8) * (-a + 10);
// a = 8
// -a = -8
// -a + 10 = 2
// 8 * 2 = 16
// b = 16int a = 8, b = 5;
System.out.println((double) a / b); // 1.6
[!TIP]
不要过多的依赖运算的优先级来控制表达式的执行顺序,这样可读性太差,使用
()来控制表达式的执行顺序。括号可以嵌套。不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。
