4366 2018-04-01 2020-06-25
前言:没准你也能学到什么,不信?
一、标识符
标识符是Java程序的重要组成部分,用来标识包、类、对象、变量、参数、方法等,可以由任意顺序的大小写字母、数字、下划线(_)和美元符号组成($)组成,但不能以数字开头,且不能是Java中的关键字。
二、数据类型*
1、概述
数据类型表示数据的性质、占用的内存空间大小及其存放方式。Java数据类型分为基本数据类型和引用数据类型,Java的数据类型,如图所示
2、整数类型
整数类型表示整数数据,即没有小数部分的值。在Java中为了给不同大小范围内的整数合理地分配存储空间,整数类型分为4种不同类型:字节型(byte)、短整型(short)、整型(int)、长整型(long),这4种类型所占内存空间大小即取值范围如下所示
类型名 | 占用内存空间 | 取值范围 |
---|---|---|
byte | 8bit(1字节) | -2^7 ~ 2^7 - 1,即-128 ~ 127 |
short | 16bit(2字节) | -2^15 ~ 2^15 - 1,即-32768 ~ 32767 |
int | 32bit(4字节) | -2^31 ~ 2^31 - 1,即-21 4748 3648 ~ 21 4748 3647 |
long | 64bit(8字节) | -2^63 ~ 2^63 - 1 |
以byte为例,为什么是2的7次方呢?不是2的8次方呢?其实位数是从0开始的,即2的0次方等于1,所以到2的7次方时,已经用到了8位了。
为什么是-128~127呢?首选要明白计算机中数据分为有符号数和无符号数,对于有符号数,计算机规定用最高位来表示符号。“0”表示正数,“1”表示负数。故能表示的最大值应为0111 1111 = 2^7 - 1= 127,最小值应为1000 0000,需要注意的是,这个值是补码,减一取反有0111 1111 ->1000 0000,最后加上负号,有-128。
如果觉得上面过程难记的话,可以这样理解:首先给定8个位,有一位要用来表示符号,那么只剩下7位,7位能表示的最大值就是2^7 - 1。由于0~2^7 - 1中间有2^7个数字,两边数字个数应该相等,因此有-1 ~-2^7。
3、浮点类型
浮点类型是包含小数部分的数据,Java中共两种浮点类型:单精度型(float)和双精度型(double)。单精度型数据占4个字节,数据精度比较低;双精度型数据占8个字节,数值的精度高,是单精度型数据的两倍。两种数据所占内存空间的大小及取值范围如下
类型名 | 占用内存空间 | 取值范围 |
---|---|---|
float | 32bit(4字节) | 1.410^-45 ~ 3.410^38,-1.410^-45 ~ -3.410^38 |
double | 64bit(8字节) | 4.910^-324 ~ 1.710^308,-4.910^-324 ~ -1.710^308 |
注意,float的范围是包含long的,因为二者的位所表示的含义不一样,具体原因读者可以自行查看相关资料。
4、字符类型*
类型名 | 占用内存空间 | 取值范围 |
---|---|---|
char | 16bit(2字节) | 0 ~ 65535,和short占通用的空间,但表示范围不同 |
- ASCII
我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)只有0和1两种状态,因此八个二进制位就可以组合出 2^8 = 256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。
上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。英语用128个符号编码就够了,但是用来表示其他语言,128个符号是远远不够的。
- Unicode
为了解决世界上各语言的编码统一问题,Unicode出现了。Unicode只是一个符号集,它只规定了符号的二进制代码。假设Unicode最大用到了4字节表示一个符号,那么它最大支持2 ^ 32 = 42.9亿种符号(去掉一个长度统计字节,有2^24=1.677千万),因此理论上可以覆盖全球所有的字符。理论很完美,但面临着如下两个问题:
-
如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
-
我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出两三倍,这是无法接受的。
- UTF-8
UTF-8(8-bit Unicode Transformation Format)是Unicode的实现方式之一,现已被互联网普遍接受(Unicode包含ASCII)。UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度(通俗的理解,UTF-8编码中Unicode最小值为0,最大值为2^24 - 1=1.677千万)。
UTF-8 的编码规则很简单,只有两条:
-
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
-
对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下面是一个实例演示
# vi utf8.txt
abc xyz 012 中国人 龍
~!@ ~!@ 987 外国人 鳯
# 通过hexdump查看
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 61 62 63 20 78 79 7A 20 30 31 32 20 E4 B8 AD E5 abc.xyz.012.d8-e
00000010: 9B BD E4 BA BA 20 E9 BE 8D 0A 7E 21 40 20 EF BD .=d::.i>..~!@.o=
00000020: 9E EF BC 81 40 20 39 38 37 20 E5 A4 96 E5 9B BD .o<.@.987.e$.e.=
00000030: E4 BA BA 20 E9 B3 AF d::.i3/
# 翻译如下,以utf-8解码,两位字符代表一字节
# 第一行,前9个都为单字节(忽略空格)
61=a, 62=b, 63=c, 20=英文空格, 78 79 7a = x y z, 30 31 32 = 0 1 2
# 第一行,后4个都为多字节,通过观察可推测 中=E4 B8 AD, 国=E5 9B BD, 人=E4 BA BA, 其他略
中国人=E4 B8 AD E5 9B BD E4 BA BA, 龍=E9 BE 8D, 英文换行=0A
以下为更为详细的解释(在线网站)
字符 | 编码10进制 | 编码16进制 | Unicode编码10进制 | Unicode编码16进制 |
---|---|---|---|---|
中 | 14989485,实际占用3字节 | E4B8AD | 20013 | 4E2D |
国 | 15047613,实际占用3字节 | E59BBD | 22269 | 56FD |
人 | 14990010,实际占用3字节 | E4BABA | 20154 | 4EBA |
龍 | 15318669,实际占用3字节 | E9BE8D | 40845 | 9F8D |
鳯 | 15315887,实际占用3字节 | E9B3AF | 40175 | 9CEF |
~ | 126,实际占用1字节 | 7E | 126 | 7E |
- Java中的字符
字符类型表示单个Unicode编码中的字符,在Java中用char表示。注意,Java中字符最大能表示Unicode值为2^16 - 1 = 65535,小于UTF-8理论最大值2^24 - 1=1.677千万。
中国汉字个数约10万个,而中文字符在utf8的编码位置是4e00-9fa5, 一共收录 20901个中文字符。因此有些汉字是不能用UTF-8表示的,这时只能采用国产编码GBK。
为什么不是理论上的最大值呢?这是因为已经够用了,美国人采用了一百多个,我们中国就用了两万多。可以通过以下代码验证
// 编译通过 2^16 - 1 = 65535,也侧面反映short与char不能相互转换,范围不同,short != char
char a = 65535;
// 编译不通过
char aa = 66636;
// 注意这里的0 ~ 65535指的是 Unicode编码10进制 的值,而不是字节的值
// 龍,所以内部存储的实际字节一定是2字节
char lon = 40845;
public static void main(String[] args) throws UnsupportedEncodingException {
String a = "中";
byte[] bytes = a.getBytes();
for (byte b : bytes) {
System.out.print(Integer.toBinaryString(b).substring(24));
}
System.out.println("\n" + Integer.toBinaryString(14989485));
System.out.println(Integer.toBinaryString(14989485).length());
}
// 输出如下
111001001011100010101101
111001001011100010101101
24
从上面的例子可以看出,当从字节流转换成字符流时,转换程序需要足够聪明,以便于识别各字节在所属字符中的含义,确保不会出现字节越界。
// 所以我们需要告诉转换程序关于各字符分界的规则
// 规则的不同,转换的结果也不同,所以就会出现乱码的情况,比如将UTF-8字节流转化成GBK
InputStreamReader inputFileReader = new InputStreamReader(upload.getInputStream(), "UTF-8");
5、包装类型
对于所有的基本类型,都有相应的包装类型,如int-Integer、char-Character、long-Long,下面以int和Integer为例进行说明。
Integer是int的包装类,int则是java的一种基本数据类型 。Integer变量必须实例化后才能使用,而int变量不需要。所以Integer默认值为null,int默认值为0。
比如说一名学生没来参加考试,另一名学生参加考试全答错了,那么第一名考生的成绩应该是null,第二名考生的成绩应该是0分。
特别的,对于Integer和int的比较,又有如下几个需要注意的点
- 由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); // false
- Integer变量和int变量比较时,只要两个变量的值是相等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)。
Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); // true
- 非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)。
Integer i = new Integer(100);
Integer j = 100; // 等同于int j = 100
System.out.print(i == j); // false
- 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
Integer i = 100;
Integer j = 100;
System.out.print(i == j); // true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); // false
对于第4条的原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
三、常量
1、整型常量
整型常量表示整型数据,有二进制、八进制、十进制、十六进制4种表示形式,具体表示如下
- 二进制(binary):以2为基数,由数字0和1组成的数字序列,前面用0b或0B开头。
- 八进制(octonary):以8为基数,由0开头,其后是由0~7范围内的整数组成的数字序列。
- 十六进制(hexadecimal):以16为基数,由0x或0X开头,其后由0~9,A~F(或a~f)范围内的整数或字母组成的序列。
Integer类内部定义了一系列方法可以实现进制之间的转换。下面是几个简单的例子
public static void main(String[] args) {
int a = 0b1100100;
int aa = 0144;
int aaa = 100;
int aaaa = 0x64;
System.out.print(a == aa && aa == aaa && aaa == aaaa);
System.out.print(Integer.toOctalString(a));
System.out.print(Integer.toString(aa));
System.out.print(Integer.toHexString(aaa));
// true 144 100 64
}
2、浮点型常量
浮点型常量分为单精度浮点型常量(float)和双精度浮点型常量(double)。浮点型常量只采用十进制数表示,其表示方式可以采用小数和指数两种形式。
- 小数形式:由数字和小数点组成。
- 指数形式:由数字和字母e(或E)组成,e或E的前后必须有数字,并且e或E之后的数字必须为整数。如1.34e6,值为1340000.0。
Java浮点型常量类默认为双精度浮点型(double)。如果要表示单精度浮点型常量,在单精度浮点型常量后面必须添加字母F(或f)。也可在浮点型常量后面添加字母D(或d),表示双精度浮点型常量。
3、字符型常量
由于Unicode字符在计算机中用二进制表示,因此字符型常量还可以用整数并表示。例如
- ‘a’,‘7’,‘{’,‘#’, ‘黄’,‘汉’,’坉’
上述的这些字符用整数表示,分别是
- 97,55,123,35, 40644,27721,22345
即有如下转换
char a = 97; // 等效于char a = 'a';
char b = 40644; // 等效于char b = '黄';
char c = '坉'; // 等效于char c = 22345;
四、变量的类型转换
在为变量赋值过程中,遵循数据类型一致性原则,即赋值运算符两边的数据类型保持一致。如果出现赋值时数据类型不一致的情况,Java提供了两种机制解决此类问题,一是自动转换,二是强制转换。
1、自动转换
自动转换又称隐式类型转换,由系统自动完成类型转换,而不需要显式地进行声明。要实现类型自动转换必须同时满足两个条件:第一是两种数据彼此兼容;第二是目标数据类型的取值范围大于源数据类型的取值范围。
- 整型之间的转换
byte -> short -> int -> long
short, char -> int, long
int -> long
// 即有如下转换
char a = 97;
int aa = a + 3; // 100
- 整型转换为浮点型
byte, short, char, int, long -> float
byte, short, char, int, long, float -> double
2、强制转换
在对变量进行类型强制转换时,会发生取值范围较大的数据类型向取值范围较小的数据类型的转换,这样做可能导致数据精度的丢失。反过来,就是等效于上面的自动转换了。
五、表达式类型提升
在进行表达式求值时,Java会自动将每个byte、short、char型操作数提升为int型(这个跟字节码指令有关)。例如:
byte a = 1, b = 2;
a = a + b; // 编译错误,int不能转byte
a = (byte)(a + b); // 通过
值得注意的是,下面的代码也会通过
a += b; // +=是java语言规定的运算符,java 编译器会对它进行特殊处理,因此可以正确编译。
如果表达式同时出现int,long,float,double中的任意两种数据类型,则Java先将取值范围小的数据类型转换为另一个取值范围大的数据类型,再进行运算,最后表达式的数据类型为取值范围大的数据类型。例如:
1 + 2.0 // 表达式的数据类型为double
1 + 2.0f // float
2.0f + 2.0 // double
(byte)2 + 3 // int
六、运算符
1、优先级
下面是运算符的优先级列表
优先级 | 运算符 |
---|---|
1 | . [] () |
2 | ++ -- ~ ! (自增的优先级还是蛮高的) |
3 | / * % (数学运算符次之) |
4 | + - |
5 | << >> >>> (位运算次之) |
6 | < <= >>= == != (等于之类的符号次之) |
7 | & ^ | && || ?: (三目运算符) |
8 | = *= /= %= += -= <<= >>= >>>= &= ^= |= |
由上表可以得出一个结论(让我想一下):括号之类的在最前面,对单个数据操作的第二,数学运算第三,位运算第四,等于判断第五,或且第六,链式等于第七。个人觉得需要特别注意的是第二优先级和第七优先级。下面是一段简单的测试代码
// 对于后自增的理解,就是进行一次运算后加1
int i = 0, j = 0;
System.out.println(i++ == i); // 0 == 1 false
System.out.println(i + j++ == 2); // 1 + 0 == 2 false
2、异或运算符
公式为 a ⊕ b = (¬a ∧ b) ∨ (a ∧¬b)。
异或运算是不同为真,相同为假。例如
true ^ true = false;
true ^ false = true;
如果觉得上面有点难记的话,可以这样想,异或运算符,肯定表面值要为真,所以异或为真,类比一下就是不同为真,相同为假(家庭中两个开关控制一个灯泡,运用的就是异或电路,很方便的)。
3、与和短路与
与运算(&)和短路与运算(&&)处理的结果是一样的,但处理机制不一样。
- 在“&”运算中,先运算两边的表达式,再运算“&”的结果。
- 而在“&&”运算中,先运算左边的表达式,如果该表达式能决定“&&”表达式的值(为假),则不运算右边的表达式。。
- 或运算(“|”)与短路或(“||”)的区别也是一样的。
4、移位运算
- 左移(<<):表示向左移n位,右边空位补0,左边移走部分舍去。
- 右移(>>):表示向右移n位,右边移走部分舍去,左边空位根据原数的符号位补0或者1(原来是负数补1,整数补0)。
- 无符号右移(>>>): 表示向右移n位,右边移走部分舍去,左边的空位补0.
10 << 1 = 1010 << 1 = 10100 = 20 // 相当于乘2的1次方
10 << 2 = 1010 << 2 = 101000 = 40 // 相当于乘2的2次方
// 对于整数,右移和无符号右移是等效的,这里不讨论负数
10 >> 1 = 1010 >> 1 = 101 = 5
10 >> 2 = 1010 >> 2 = 10 = 2
10 >> 5 = 1010 >> 5 = 0 = 0
10 >>> 1 = 1010 >>> 1 = 101 = 5
10 >>> 2 = 1010 >>> 2 = 10 = 2
10 >>> 2 = 1010 >>> 5 = 0 = 0
七、有趣的题目
System.out.println("2 + 2" + 2 + 2);//2 + 222 String + int + int = String + String + String
System.out.println(2 + "2 + 2" + 2);//22 + 22 int + String + int = String + String + String
System.out.println(2 + 2 + "2 + 2");//42 + 2 int + int + String = int + String = String + String
// 遵循从左到右,类型自动升级的机制
int a = 5;
System.out.println("value is " + ((a < 5) ? 10.9 : 9));// 9.0
// 表达式自动提升
char x = 'x';
int i = 0;
System.out.println(false ? i : x); // 120
System.out.println(false ? 10 : x); // x
// false ? 常量x : 变量T,如果x可以被变量T表示,则结果为T类型(10可以被char表示)
// 也适用于类型升级机制
请仔细思考下面的输出值(假设猜中了有100块钱)
private static void test() {
int a = 10 >> 1;
int b = a++;
int c = ++a;
int d = b * a++;
System.out.print(a + " ");
System.out.print(b + " ");
System.out.print(c + " ");
System.out.print(d);
}
// 7 8 25 8 5 7 35 8 9 21
// 输出结果为上述数字中连续4个数和为55
总访问次数: 266次, 一般般帅 创建于 2018-04-01, 最后更新于 2020-06-25
欢迎关注微信公众号,第一时间掌握最新动态!