数据类型
一、为什么需要定义数据类型?
Edwin Brady 在《Type-driven Development with Idris》中的说法,类型有以下几个重要角色:
- 对机器而言,类型描述了内存中的电荷是怎么解释的。
- 对编译器或者解释器而言,类型可以协助确保上面那些电荷、字节在程序的运行中始终如一地被理解。
- 对程序员而言,类型可以帮助他们命名、组织概念,帮助编纂文档,支持交互式编辑环境等。
Types serve several important roles: For a machine, types describe how bit patterns in memory are to be interpreted. For a compiler or interpreter, types help ensure that bit patterns are interpreted consistently when a program runs. For a programmer, types help name and organize concepts, aiding documentation and supporting interactive editing environments.
二、数据类型的分类
1、数据类型分类
1. 基本数据类型:byte,short,int,long,float,double,chart,boolean
-
引用数据类型:类,接口,数组,枚举,注解
-
基本数据类型与引用数据类型区别
基本数据类型存储在栈中,引用数据类型存储在堆中。
2、基本数据类型
1、整数类型
-
定义:整数类型用来存储整数数值,即没有小数部分的数值,可以是正数,也可以是负数。
-
表示形式:
-
十进制:平时读写的数值都是以十进制进行计数的,如:12,23,333
注:不能以0作为十进制的开头(0除外)
-
八进制:如0123(转换为十进制数为83)、-123(十进制数为-83)
注:八进制以0开头
-
十六进制:如0x25(十进制数为37)、0Xb01e(十进制数为45086)
注:十六进制以0x或0X开头
-
进制转换
如八进制数0123
我们从低位开始
3*8^0+2*8^1+1*8^2=83 其它进制以此类推
-
-
整形数据类型内存空间和大小
数据类型 内存空间(8位1字节) 取值范围 byte 8bit -128~127 short 16bit -32768~32767 int 32bit -232~232-1 long 64bit -264~264-1
对于数值类型的基本类型的取值范围,我们无需强制去记忆,因为它们的值都已经以常量的形式定义在对应的包装类中了
基本类型byte 二进制位数:Byte.SIZE最小值:Byte.MIN_VALUE最大值:Byte.MAX_VALUE
基本类型short二进制位数:Short.SIZE最小值:Short.MIN_VALUE最大值:Short.MAX_VALUE
基本类型char二进制位数:Character.SIZE最小值:Character.MIN_VALUE最大值:Character.MAX_VALUE
基本类型double 二进制位数:Double.SIZE最小值:Double.MIN_VALUE最大值:Double.MAX_VALUE
注:float、double两种类型的最小值与Float.MIN_VALUE、 Double.MIN_VALUE的值并不相同,实际上Float.MIN_VALUE和Double.MIN_VALUE分别指的是 float和double类型所能表示的最小正数。也就是说存在这样一种情况,0到±Float.MIN_VALUE之间的值float类型无法表示,0 到±Double.MIN_VALUE之间的值double类型无法表示。这并没有什么好奇怪的,因为这些范围内的数值超出了它们的精度范围。
Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。
此外:
Java基本类型存储在栈中,因此它们的存取速度要快于存储在堆中的对应包装类的实例对象。从Java5.0(1.5)开始,JAVA虚拟机(Java Virtual Machine)可以完成基本类型和它们对应包装类之间的自动转换。因此我们在赋值、参数传递以及数学运算的时候像使用基本类型一样使用它们的包装类,但这并不意味着你可以通过基本类型调用它们的包装类才具有的方法。另外,所有基本类型(包括void)的包装类都使用了final修饰,因此我们无法继承它们扩展新的类,也无法重写它们的任何方法。
-
实例代码:
public class Number{ public static void main(String[] args){ int x; int x,y; int x=45,=-32; int x=y=32; System.out.println(x); System.out.println(y); } }
2、浮点数
-
定义:浮点数表示有小数部分的数值
-
浮点数内存空间及大小
数据类型 内存空间 取值范围 float 32bit 1.4E-45~3.4028235E38 double 63位 4.9E-324~1.797693134862357E308 注:默认情况下小数都被看做double类型,若使用float型小数,则需要在小数后面添加F或f。可以使用后缀d或D来明确表示double类型。
-
代码实例:
float f1 = 123.23f; double d1 = 1232.123; double d2 = 2312.23d;
3、字符类型(char)
-
定义:字符类型用于存储单个字符,占用16位(两个字节)。在定义字符型变量时要以单引号表示,如’s’表示一个字符,而“s”表示一个字符串
-
声明字符型
char x = 'a'; 由于字符型a在Unicode表中的位置是97,因此 char x = 97;
-
Unicode编码
同C和C++一样,java语言也可以把字符作为整数对待。由于Unicode编码采用无符号编码,可以存错65536个字符(0x0000~0xffff),所以java中的字符几乎可以处理所有国家的语言文字。
-
转义字符
转义字符 意义 ASCII码值(十进制) \a 响铃(BEL) 007 \b 退格(BS) ,将当前位置移到前一列 008 \f 换页(FF),将当前位置移到下页开头 012 \n 换行**(LF)** ,将当前位置移到下一行开头 010 \r 回车**(CR)** ,将当前位置移到本行开头 013 \t 水平制表**(HT)** (跳到下一个TAB位置) 009 \v 垂直制表(VT) 011 \ 代表一个反斜线字符**’’’** 092 ’ 代表一个单引号(撇号)字符 039 " 代表一个双引号字符 034 \0 空字符(NULL) 000 \ddd 1到3位八进制数所代表的任意字符 三位八进制 \xhh 1到2位十六进制所代表的任意字符 二位十六进制 注意:区分,斜杠:"/" 与 反斜杠:"" ,此处不可互换
点的转义:. ==> u002E
美元符号的转义:$ ==> u0024
乘方符号的转义:^ ==> u005E
左大括号的转义:{ ==> u007B
左方括号的转义:[ ==> u005B
左圆括号的转义:( ==> u0028
竖线的转义:| ==> u007C
右圆括号的转义:) ==> u0029
星号的转义:* ==> u002A
加号的转义:+ ==> u002B
问号的转义:? ==> u003F
反斜杠的转义:/==> u005C
4、布尔类型
- 定义:布尔类型又称逻辑类型,通过关键字boolean来进行定义,只有true和false两个值,分表代表逻辑中的“真”和“假”。
5、数据类型默认值
下表列出了 Java 各个类型的默认值:
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | ‘u0000’ |
String (or any object) | null |
boolean | false |
引用数据类型
-
在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
-
对象、数组都是引用数据类型。
-
所有引用类型的默认值都是null。
-
一个引用变量可以用来引用任何与之兼容的类型。
-
例子:Student stu = new Student(); //new 在内存的堆空间创建对象
stu.study(); //把对象的地址赋给stu引用变量
上例实现步骤:a.JVM加载Student.class 到Code区
b.new Student()在堆空间分配空间并创建一个Student实例
c.将此实例的地址赋值给引用stu, 栈空间
三、变量与常量
1、标识符
-
定义:表示符可以理解为名字,用来标识类名、变量名、方法名、数组名等有效字符序列
-
合法标识符
java规定:标识符由任意顺序的字母、下划线、美元符号和数字组成,并且第一个不能是数字,标识符也不能是java中的保留关键字
-
关键字
abstract | assert | boolean | break | byte |
---|---|---|---|---|
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
2、声明变量
-
定义:变量在java使用率很高,变量的声明要指明变量需要声明的类型,这样编译器才知道为他分配多少空间,基础数据类型有默认的初始值,但建议还是自己赋初始值,在声明是需要给你的变量取名字,这个名字要符合标识符的规范
-
变量的作用域
变量分为全局变量、局部变量和静态变量,
全局变量:全局变量在类作用域类都是有效的所属于某个对象对象间是不可见的
局部变量:局部变量只在它定义的方法或方法块里有效,如:方法的参数等
静态变量:静态变量是一种全局变量,他比较特殊,他是存储于方法区中,静态变量随着类的加载而存在,
所有类共享这个变量
3、声明常量
-
定义:在程序中一直不会改变的量称为常量,通常也被称为“final变量”。常量在整个程序中只能被赋值一次,在为所有的对象共享值时,常量非常有用。
-
语法:
final 数据类型 常量名 = 值; //常量名一般采用大写,用下划线进行连接
注: 当定义的final变量为成员变量时,必须为他设置初始值。
四、运算符
1、赋值运算符
- 定义:赋值运算符以符号“=”表示,他是个二元运算符(对两个操作数做处理)
- 作用:就是将运算符右边的操作数赋值给左边(不展开,大家应该都懂)
2、算术运算符
-
定义:算术运算符主要有加、减、乘、除、求余操作
-
java算术运算符
操作符 描述 例子 + 加法 - 相加运算符两侧的值 A + B 等于 30 - 减法 - 左操作数减去右操作数 A – B 等于 -10 * 乘法 - 相乘操作符两侧的值 A * B等于200 / 除法 - 左操作数除以右操作数 B / A等于2 % 取余 - 左操作数除以右操作数的余数 B%A等于0 -
代码实例
public class study_day01 { public static void main(String[] args) { int a = 23; int b = 4; int c = 4; System.out.println("a + b = " + (a + b) ); System.out.println("a - b = " + (a - b) ); System.out.println("a * b = " + (a * b) ); System.out.println("b / a = " + (b / a) ); System.out.println("b % a = " + (b % a) ); System.out.println("c % a = " + (c % b) ); } }
结果:
3、自增和自减运算符
-
定义:自增、自减是单目运算符,可以在操作元素之前,也可以在操作元素之后,操作元素必须是一个整型或浮点型变量。
-
java描述
符 描述 例子 ++ 自增: 操作数的值增加1 B++ 或 ++B 等于 21 – 自减: 操作数的值减少1 B-- 或 --B 等于 19 -
前缀和后缀的区别
直接代码演示
public class study_day01 { public static void main(String[] args) { int a = 23; int d = 25; System.out.println("a++ = " + (a++) ); System.out.println("a-- = " + (a--) ); // 查看 d++ 与 ++d 的不同 System.out.println("d++ = " + (d++) ); System.out.println("++d = " + (++d) ); } }
结果:
为什么?
-
int a=3,c=3
-
int b = ++a; 拆分运算过程为: a=a+1=4; b=a=4, 最后结果为b=4,a=4
-
int d = --c; 拆分运算过程为: c=c-1=2; d=c=2, 最后结果为d=2,c=2
-
int b = a++; 拆分运算过程为: ; b=a=3,a=a+1=4, 最后结果为b=3,a=4
-
int d = c–; 拆分运算过程为:d=c=3, c=c-1=2; 最后结果为d=3,c=2
前缀自增自减法(++a,–a): 先进行自增或者自减运算,再进行表达式运算。
后缀自增自减法(a++,a–): 先进行表达式运算,再进行自增或者自减运算
4、关系运算符
-
定义:用于程序中的变量之间、变量和自变量之间以及其它类型的信息之间的比较,关系运算符的运算结果是boolean类型。
-
java中比较运算符
运算符 描述 例子 == 检查如果两个操作数的值是否相等,如果相等则条件为真。 (A == B)为假。 != 检查如果两个操作数的值是否相等,如果值不相等则条件为真。 (A != B) 为真。 > 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。 (A> B)为假。 < 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。 (A <B)为真。 >= 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。 (A> = B)为假。 <= 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。 (A <= B)为真。 -
代码实例
public static void compare(){ int a = 4; int b = 5; System.out.println("a == b = " + (a == b) ); System.out.println("a != b = " + (a != b) ); System.out.println("a > b = " + (a > b) ); System.out.println("a < b = " + (a < b) ); System.out.println("b >= a = " + (b >= a) ); System.out.println("b <= a = " + (b <= a) ); }
结果:
5、位运算符
Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。
位运算符作用在所有的位上,并且按位运算。假设a = 60,b = 13;它们的二进制格式表示将如下:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A | B = 0011 1101
A ^ B = 0011 0001
~A= 1100 0011
基本位运算符:
下表列出了位运算符的基本运算,假设整数变量 A 的值为 60 和变量 B 的值为 13:
操作符 | 描述 | 例子 |
---|---|---|
& | 如果相对应位都是1,则结果为1,否则为0 | (A&B),得到12,即0000 1100 |
| | 如果相对应位都是 0,则结果为 0,否则为 1 | (A | B)得到61,即 0011 1101 |
^ | 如果相对应位值相同,则结果为0,否则为1 | (A ^ B)得到49,即 0011 0001 |
〜 | 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 | (〜A)得到-61,即1100 0011 |
<< | 按位左移运算符。左操作数按位左移右操作数指定的位数。 | A << 2得到240,即 1111 0000 |
>> | 按位右移运算符。左操作数按位右移右操作数指定的位数。 | A >> 2得到15即 1111 |
>>> | 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | A>>>2得到15即0000 1111 |
注:移位可以实现整数除以或乘以2的n次方的效果,例如y<<2与y*4的结果一样;y>>1的结果与y/2的结果一样。
6、条件运算符
-
使用格式
条件式 ? 值1 :值2 variable x = (expression) ? value if true : value if false
-
代码实例
public static void main(String[] args){
int a , b;
a = 10;
// 如果 a 等于 1 成立,则设置 b 为 20,否则为 30
b = (a == 1) ? 20 : 30;
System.out.println( "Value of b is : " + b );
// 如果 a 等于 10 成立,则设置 b 为 20,否则为 30
b = (a == 10) ? 20 : 30;
System.out.println( "Value of b is : " + b );
}
结果:
7、instanceof 运算符
该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。
-
使用格式
( Object reference variable ) instanceof (class/interface type)
如果运算符左侧变量所指的对象,是操作符右侧类或接口(class/interface)的一个对象,那么结果为真。
String name = "James";
boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
如果被比较的对象兼容于右侧类型,该运算符仍然返回true。
class Vehicle {}
public class Car extends Vehicle {
public static void main(String[] args){
Vehicle a = new Car();
boolean result = a instanceof Car;
System.out.println( result);//输出true
}
}
8、运算符优先级
下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。
类别 | 操作符 | 关联性 |
---|---|---|
后缀 | () [] . (点操作符) | 左到右 |
一元 | expr++ expr– | 从左到右 |
一元 | ++expr --expr + - ~ ! | 从右到左 |
乘性 | * /% | 左到右 |
加性 | + - | 左到右 |
移位 | >> >>> << | 左到右 |
关系 | > >= < <= | 左到右 |
相等 | == != | 左到右 |
按位与 | & | 左到右 |
按位异或 | ^ | 左到右 |
按位或 | | | 左到右 |
逻辑与 | && | 左到右 |
逻辑或 | | | | 左到右 |
条件 | ?: | 从右到左 |
赋值 | = + = - = * = / =%= >> = << =&= ^ = | = | 从右到左 |
逗号 | , | 左到右 |
五、类型转换
1、基本数据类型转换
a. 基本数据类型中类型的自动提升
数值类型在内存中直接存储其本身的值,对于不同的数值类型,内存中会分配相应的大小去存储。如:byte类型的变量占用8位,int类型变量占用32位等。相应的,不同的数值类型会有与其存储空间相匹配的取值范围。具体如下所示:
图中依次表示了各数值类型的字节数和相应的取值范围。在Java中,整数类型(byte/short/int/long)中,对于未声明数据类型的整形,其默认类型为int型。在浮点类型(float/double)中,对于未声明数据类型的浮点型,默认为double型。
看下面的例子
是不是有点奇怪?按照上面的思路去理解,将一个int型的1000赋给一个byte型的变量a,提示出错,但是最后一句:将一个int型的3赋给一个byte型的变量c,居然编译正确,这是为什么呢?
原因在于:jvm在编译过程中,对于默认为int类型的数值时,当赋给一个比int型数值范围小的数值类型变量(在此统一称为数值类型k,k可以是byte/char/short类型),会进行判断,如果此int型数值超过数值类型k,那么会直接编译出错。因为你将一个超过了范围的数值赋给类型为k的变量,k装不下嘛,你有没有进行强制类型转换,当然报错了。但是如果此int型数值尚在数值类型k范围内,jvm会自定进行一次隐式类型转换,将此int型数值转换成类型k。如图中的虚线箭头。这一点有点特别,需要稍微注意下。
另外在IDEA中, 类型的判断会在写程序时由编辑器帮你做判断, 而不需要到编译的时候由编译器来做判断, 这也是IDEA的一个优点.
在其他情况下,当将一个数值范围小的类型赋给一个数值范围大的数值型变量,jvm在编译过程中俊将此数值的类型进行了自动提升。在数值类型的自动类型提升过程中,数值精度至少不应该降低(整型保持不变,float->double精度将变高)。
如上:定义long类型的a变量时,将编译出错,原因在于11111111111默认是int类型,同时int类型的数值范围是-2^31 ~ 2^31-1,因此,11111111111已经超过此范围内的最大值,故而其自身已经编译出错,更谈不上赋值给long型变量a了。
此时,若想正确赋值,改变11111111111自身默认的类型即可,直接改成11111111111L即可将其自身类型定义为long型。此时再赋值编译正确。
将值为10的int型变量 z 赋值给long型变量q,按照上文所述,此时直接发生了自动类型提升, 编译正确。
接下来,还有一个地方需要注意的是:char型其本身是unsigned型,同时具有两个字节,其数值范围是0 ~ 2^16-1,因为,这直接导致byte型不能自动类型提升到char,char和short直接也不会发生自动类型提升(因为负数的问题),同时,byte当然可以直接提升到short型。
b. 隐式类型转换
上面的例子中既有隐式类型转换, 也有强制类型转换, 那么什么是隐式类型转换呢?
隐式转换也叫作自动类型转换, 由系统自动完成.
从存储范围小的类型到存储范围大的类型.
byte ->short(char)->int->long->float->double
c. 显示类型转换
显示类型转换也叫作强制类型转换, 是从存储范围大的类型到存储范围小的类型.
当我们需要将数值范围较大的数值类型赋给数值范围较小的数值类型变量时,由于此时可能会丢失精度(1讲到的从int到k型的隐式转换除外),因此,需要人为进行转换。我们称之为强制类型转换。
double→float→long→int→short(char)→byte
byte a =3;编译正确在1中已经进行了解释。接下来将一个值为3的int型变量b赋值给byte型变量c,发生编译错误。这两种写法之间有什么区别呢?
**区别在于前者3是直接量,编译期间可以直接进行判定,后者b为一变量,需要到运行期间才能确定,也就是说,编译期间为以防万一,当然不可能编译通过了。**此时,需要进行强制类型转换。
强制类型转换所带来的结果是可能会丢失精度,如果此数值尚在范围较小的类型数值范围内,对于整型变量精度不变,但如果超出范围较小的类型数值范围内,很可能出现一些意外情况。
上面的例子中输出值是 -23.
为什么结果是-23?需要从最根本的二进制存储考虑。
233的二进制表示为:24位0 + 11101001,byte型只有8位,于是从高位开始舍弃,截断后剩下:11101001,由于二进制最高位1表示负数,0表示正数,其相应的负数为-23。
d. 进行数学运算时的数据类型自动提升与可能需要的强制类型转换
当进行数学运算时,数据类型会自动发生提升到运算符左右之较大者,以此类推。当将最后的运算结果赋值给指定的数值类型时,可能需要进行强制类型转换。例如:
a+b会自动提升为int, 因此在给c赋值的时候要强制转换成byte.
2、类型转换中的符号扩展Sign Extension
有没有想过这么一个问题, 当把一个byte的负数转换为int时, 它的值是正数还是负数呢? 当把一个int强制转为为byte, 我们能否确定转换后数字的符号呢? 要理解这两点, 我们首先要明白计算机中数的表示, 和java中类型转换时进行的操作.
a. 计算机中数的表示
计算机中的数都是以补码的形式存储的, 最高位是符号位. 正数的补码是它本身, 而负数的补码是原码按位取反后加1. 这样我们就很清楚java中这些数据类型的范围是怎么得到的.
例如: byte的范围是-128 ~ 127. 为什么会有-128呢? 其实-128的二进制表示是 10000000, 这个补码形式是不是很奇怪呢? 我们找不到一个数可以对应这样的补码, 其实这是-0的原码, 那-0的补码呢? 按位取反加1试试看, 是不是又变为00000000呢? 所以这个多出来的-0就用来表示-128了.
有了上面的表示, 我们就要问: 如何在类型扩展的时候保持数字的符号和值不变呢?
b. java中的符号扩展
- 什么是符号扩展
符号扩展(Sign Extension)用于在数值类型转换时扩展二进制位的长度,以保证转换后的数值和原数值的符号(正或负)和大小相同,一般用于较窄的类型(如byte)向较宽的类型(如int)转换。扩展二进制位长度指的是,在原数值的二进制位左边补齐若干个符号位(0表示正,1表示负)。
举例来说,如果用6个bit表示十进制数10,二进制码为"00 1010",如果将它进行符号扩展为16bits长度,结果是"0000 0000 0000 1010",即在左边补上10个0(因为10是正数,符号为0),符号扩展前后数值的大小和符号都保持不变;如果用10bits表示十进制数-15,使用“2的补码”编码后,二进制码为"11 1111 0001",如果将它进行符号扩展为16bits,结果是"1111 1111 1111 0001",即在左边补上6个1(因为-15是负数,符号为1),符号扩展前后数值的大小和符号都保持不变。
- java中数值类型转换的规则
这个规则是《Java解惑》总结的:如果最初的数值类型是有符号的,那么就执行符号扩展;如果是char类型,那么不管它要被转换成什么类型,都执行零扩展。还有另外一条规则也需要记住,如果目标类型的长度小于源类型的长度,则直接截取目标类型的长度。例如将int型转换成byte型,直接截取int型的右边8位。
所以java在进行类型扩展时候会根据原始数据类型, 来执行符号扩展还是零扩展. 数值类型转数值类型的符号扩展不会改变值的符号和大小.
c. 解析“多重转型”问题
一个连续三次类型转换的表达式如下:
1. int(32位) -> byte(8位)
-1是int型的字面量,根据“2的补码”编码规则,编码结果为0xffffffff,即32位全部置1.转换成byte类型时,直接截取最后8位,所以byte结果为0xff,对应的十进制值是-1.
2. byte (8位) -> char (16位)
由于byte是有符号类型,所以在转换成char型(16位)时需要进行符号扩展,即在0xff左边连续补上8个1(1是0xff的符号位),结果是0xffff。由于char是无符号类型,所以0xffff表示的十进制数是65535。
3. char (16位) -> int (32位)
由于char是无符号类型,转换成int型时进行零扩展,即在0xffff左边连续补上16个0,结果是0x0000ffff,对应的十进制数是65535。
d. 几个转型的例子
在进行类型转换时,一定要了解表达式的含义,不能光靠感觉。最好的方法是将你的意图明确表达出来。
在将一个char型数值c转型为一个宽度更宽的类型时,并且不希望有符号扩展,可以如下编码:
上文曾提到过,0xffff是int型字面量,所以在进行&操作之前,编译器会自动将c转型成int型,即在c的二进制编码前添加16个0,然后再和0xffff进行&操作,所表达的意图是强制将前16置0,后16位保持不变。虽然这个操作不是必须的,但是明确表达了不进行符号扩展的意图。
如果需要符号扩展,则可以如下编码:
首先将c转换成short类型,它和char是 等宽度的,并且是有符号类型,再将short类型转换成int类型时,会自动进行符号扩展,即如果short为负数,则在左边补上16个1,否则补上16个0.
如果在将一个byte数值b转型为一个char时,并且不希望有符号扩展,那么必须使用一个位掩码来限制它:
(b & 0xff)的结果是32位的int类型,前24被强制置0,后8位保持不变,然后转换成char型时,直接截取后16位。这样不管b是正数还是负数,转换成char时,都相当于是在左边补上8个0,即进行零扩展而不是符号扩展。
如果需要符号扩展,则编码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRYKxDFD-1605934052309)(http://qjvlc1ep3.hn-bkt.clouddn.com/blog/693250-20161027220240890-1016627794.png)]
此时为了明确表达需要符号扩展的意图,注释是必须的。
e.总结
实际上在数值类型转换时,只有当遇到负数时才会出现问题,根本原因就是Java中的负数不是采用直观的方式进行编码,而是采用“2的补码”方式,这样的好处是加法和减法操作可以同时使用加法电路完成,但是在开发时却会遇到很多奇怪的问题,例如(byte)128的结果是-128,即一个大的正数,截断后却变成了负数
参考博文:
java从入门到精通