Java_基本数据类型、运算符、数组(1)

基本数据类型

整数型

1
2
3
byte b = 123; //8位,范围-128到127
short s = 32767; //16位,范围-32768到32767
int i = 1234567891; //32位,字面量整数的默认类型,范围-2147483648到2147483647
1
2
3
long l = 2147483648L; //64位,最好在末尾加上L
long l = 2147483647;  //不加L则默认为int,在int范围内才能通过编译
long l = 2147483648;  //超出int范围则会编译报错Integer number too large

对于 byte 和 short,若字面量整数在范围内,则自动进行类型转换,编译不会报错

若超出了范围,编译则会报错,可对其进行强制类型转换,不过会丢失精度

1
byte b = (byte)130; //b = -126

130 的补码为 00000000 00000000 00000000 10000010,从 int 强制转换为 byte,前面三个字节舍去,得到补码 10000010,首位为 1 表示负数,所以将补码 10000010 减 1 然后再将除符号位以外其他的位取反,得到原码 11111110,可知对应的十进制数为 -126

1
byte b = (byte)-129; //b = 127

同理 -129 的补码为 11111111 11111111 11111111 01111111,从 int 强制转换为 byte,前面三个字节舍去,得到补码 01111111,首位为0 表示正数,所以原码与补码相同,对应十进制数为 127

可通过 Integer 和 Long 的静态方法查看整数的二进制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Test {
  public static void main(String[] args) {
    byte b = (byte) 130;
    //截断之后得到补码10000010,在运算的时候默认会自动提升为int,并且高位自动补1
    //11111111111111111111111110000010
    System.out.println(Integer.toBinaryString(b));
    //1111111111111111111111111111111111111111111111111111111110000010
    System.out.println(Long.toBinaryString(b));
  }
}

所以上面 130 和 -129 的例子仅仅是大概的理解,没有考虑整数在运算的时候会自动提升为 int ,并且高位会有自动补 1 的操作,对于 byte 128,printf 打印出来结果为 -128

1
byte b = (byte)128; //b = -128

128 的补码为 00000000 00000000 00000000 10000000,byte 截断之后为 10000000,此时 -1 再取反就不能像上面那样简单地分析了,考虑到会提升到默认的 int,并且在高位补 1 ,所以此时 byte 128 的补码为 11111111 11111111 11111111 10000000,即再进行减 1 后取反得到的源码为 1000 0000 0000 0000 0000 0000 1000 0000,即对应十进制数 -128

对于补码,还可以解释为是实现的一种映射,例如从[0, 255] 映射到 [-128, 127]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
+----------------------------+
| 255      -1      11111111  |
| 254      -2      11111110  |
|           ...              |
| 129      -127    10000001  |
| 128      -128    10000000  |
| 127      127     01111111  |
|           ...              |
| 1        1       00000001  |
| 0        0       00000000  |
+----------------------------+

或者是模的理解,一个二进制有多少位,对应的模就 1 后面接多少个 0,补码则是正数不变,负数为模减去绝对值,所以 -128 的补码为 100000000 - 10000000 = 10000000 (其实这个所谓的模就是底层数字电路全加器的进位)

编译器能在编译时直接计算出字面量整数相加的值,但是变量相加则会报错

1
2
3
4
5
byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2; //编译类型报错,+号计算会自动提升到默认的int
byte b3 = b1 + 2;  //编译类型报错
byte b4 = 1 + 2;   //编译期直接得到结果,不报错

还需要注意的是 byte 类型的二进制数据一致性问题

1
2
3
4
5
6
//-127补码为11111111 11111111 11111111 10000001
byte b = -127; 
//11111111 11111111 11111111 10000001 & 00000000 0000000 0000000 11111111
int i = b & 0xff;
//计算后i的补码为00000000 00000000 00000000 10000001
System.out.println(i); //129

对于 -127 的高 24 位补 1,保证了十进制的数据一致性但破坏了二进制数据的一致性,通过 & 0xff 运算将高 24 位置零,低 8 位不变,来保证原始二进制数据的一致性

Java 不提供 Unsigned 无符号类型,可通过 & 0xff 的方式来进行转换

浮点数型

IEEE 754 浮点数标准,分为符号位、指数位、尾数位,三段储存,标准形式如下

$\pm1.bbbbb\cdots*2^{bbbbb\cdots}$ ,b 为 0 或 1

例如将十进制数 15.0625 转换为二进制数为 1111.0001,然后移位得 $1.1110001*2^{11}$ ,所以保存 15.0625 的三段分别为符号位的 0,指数为的 11,尾数位的 1110001

上面仅仅是大概的理解,具体还会涉及到偏移量、舍入等问题

float 是 32 位单精度,符号 1 位,指数 8 位,尾数 23 位 double 是 64 位双精度,符号 1 位,指数 11 位,尾数 52 位

1
2
3
double d = 3.1415; //64位,字面量浮点数默认类型
float f = 3.14F; //32位,与整数型long不同,float的末尾必须加上F
//若float不加F则会编译报错,因为从double转换为float会损失精度

若整数部分超过 7 位,则会显示为科学计数法,E 为底数 10

1
2
double d1 = 1234567.1;
double d2 = 12345678.1; //1.23456781E7

Java 提供有 native 方法查看浮点数的二进制

1
2
3
4
5
6
7
8
9
public class Test {
  public static void main(String[] args) {
    //Long.toBinaryString(Double.doubleToLongBits(15.0625));
    System.out.println(Integer.toBinaryString(Float.floatToIntBits(15.0625F)));
    //符号位 指数位 尾数位
    //0 10000010 11100010000000000000000
    //指数位在保存时进行了偏移
  }
}

浮点数计算有误差,需要精确计算的场景则使用 BigDecimal

布尔型和字符型

布尔型

1
2
boolean boo1 = true;
boolean boo2 = false;

char 被设计为每个字符占 16 位,范围 0 到 65535,满足了早期的 Unicode 编码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//注意char字符是单引号
char c1 = 'a';  //97
char c2 = '中'; //20013,对应\u开头的十六进制\u4e2d
char c3 = '文'; //25991,对应\u开头的十六进制\u6587
char c4 = (char) (c2 + c3); //20013 + 25991
System.out.println(c4);  //뎴
char c5 = 20013 + 25991; //字面量在编译时就计算出值,不用像c4强转类型
System.out.println(c5);  //뎴
//超出范围就会报错 
char c1 = -1;
char c2 = 65536;
1
2
char c = (char) 85549; //int强转为char,高16位被舍去,所以实际赋值给c的是20013
System.out.println(c); //中

但之后 Unicode 扩展了,超出了 16 位的范围,char 就不能再保存超出范围的字符

1
2
char c = '𡕗'; //报错Too many characters in character literal
String s = "𡕗";

Java 8 文档 中的说明是 char[] 和 String 采用的编码方式是 UTF-16,所以非必要则一般使用 String

运算符

Java 不提供运算符重载

计算运算符

整除 /

1
2
(10 / 3) //3 整数相除会将小数舍去
(10 / 3.0) //3.3333333333333335

取模 %,公式: a % b $=a-\frac{a}{b}*b$

这里 a / b 是指取整数商,所以涉及到舍入方向的问题,分向上舍入和向下舍入两种情况

向上舍入 向下舍入 c++ Java python
5 % 8 -3 5 5 5 5
8 % 5 -2 3 3 3 3
-5 % -8 3 -5 -5 -5 -5
-8 % -5 2 -3 -3 -3 -3
-5 % 8 -5 3 -5 -5 3
5 % -8 5 -3 5 5 -3
8 % -5 3 -2 3 3 -2
-8 % 5 -3 2 -3 -3 2

python 总是向下舍入取整, c++ 和 Java 是同号向下异号向上

正整数 %2 的结果不是 0 就是 1 ,所以可以用来当作切换条件

++、–、+=

1
2
3
int x = 4;
//       4  +  6  +   60  	
int y = x++ + ++x + (x * 10); //70
1
2
3
byte b = 10;
b++; //相当于 b = (byte) (b + 1),--同理
b = b + 1; //编译类型报错
1
2
3
short s = 1;
s += 1; //
s = s + 1; //编译类型报错,与上面++同理

关系运算符和位运算符

&& || ! 分别对应与、或、非

&& 为短路逻辑与,左边是 false 则右边不执行

|| 为短路逻辑或,左边是 true 则右边不执行

! 就是 true 和 false 取反

& | ^ ~ 分别对应位与、位或、位异或、位取反

比较两个比特位,& 是都为 1 结果才为 1,否则结果为 0,| 则是只要有一个 1 则返回 1

^ 是相同为 0,不同为 1,~ 则是 0 和 1取反

不用临时变量,交换两个变量的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int a = 2;
int b = 3;
a = a + b; //a = 5, b = 3 
b = a - b; //a = 5, b = 2
a = a - b; //a = 3, b = 2

//用 ^ 实现
//x^0=x x^x=0 x^y=y^x
a = a ^ b; //a = a ^ b, b = b
b = a ^ b; //a = a ^ b, b = a ^ b ^ b 即 b = a
a = a ^ b; //a = a ^ b ^ a 即 a = b, b = a

左移 « ,左边最高位丢弃,右边最低位补 0 ,相当于乘以 2 右移 » ,最高位是 0 左边补齐 0 ,最高位是 1 则补齐 1 ,低位丢弃,相当于除以 2 无符号右移:»> 无论最高位是 0 是 1,都补齐 0

可通过位运算来提高计算效率,例如 2 * 8 可替换为 2 « 3

== 和三目运算符

关系运算符返回 boolean,比较的是值,基本类型保存的是值可以直接通过 == 比较,对于引用类型,== 比较的是引用的值,即地址,这会涉及到 hashcode 和 equals 方法的重写

三目运算符

1
2
// (关系表达式) ? 表达式1 : 表达式2;
boolean boo = (1 == 2) ? true : false;

数组

1
2
3
数据类型[] 数组名 = new 数据类型[数组长度];             //默认初始化
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ...};  //显示初始化
左边几个[]就代表几维数组

默认初始化的默认值,整型 0,浮点型 0.0,布尔型 false,字符型 ‘\u0000’

1
2
3
4
5
int[] iArr = new int[1];
System.out.println(iArr[0]); //0

int[] iArr1 = new int[] {1, 2, 3, 4}; //由元素的个数自动定义数组的长度
int[] iArr2 = {1, 2, 3, 4}; //简写形式

数组也是对象,可先声明数组引用,然后再 new 一个数组出来进行赋值

1
2
int[] iArr;
iArr = new int[] {1, 2, 3};

查看数组对象的类名

1
2
3
4
int[] iArr1 = new int[1];
int[][] iArr2 = new int[1][1];
System.out.println(iArr1.getClass().getName()); //[I
System.out.println(iArr2.getClass().getName()); //[[I 
1
2
[B      [S       [J      [F       [D        [Z         [C
byte[]  short[]  long[]  float[]  double[]  boolean[]  char[]

二维数组的第一维保存的是一维数组的引用,可以实现一行 (或一列) 的直接交换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int[][] iArr = {{1, 2}, {3, 4}, {5, 6}};
int[] tmp = iArr[0];
iArr[0] = iArr[1];
iArr[1] = tmp;
for (int i = 0; i < iArr.length; i++) {
  for (int j = 0; j < iArr[i].length; j++) {
    System.out.print(iArr[i][j] + " ");
  }
  System.out.println();
}
//3 4 
//1 2 
//5 6 

以上内容是玉山整理的笔记,如有错误还请指出