第一章 Java的数据类型转换

本章开始,将从java基础模块开始分析知识点。包括基本数据类型,包装类,String,IO,异常,线程,集合等等分析或原码分析。
我打算从这篇文章开始,逐步研习java的基础知识点,自己也在不断学习中,如果文章中出现不正确的描述,希望各路大神能多多斧正。
JAVA的类型转换是平时在coding中经常接触到的东西,我觉得如果不深入了解其原理,总有一天会掉入大坑中。所以本章将介绍java不同数据类型的类型转换和细节分析,如果能帮到你,希望能点个赞。

本章知识结构图:
在这里插入图片描述

概念介绍

所谓类型转换,即对数据的操作。java的数据类型分为两种;

1. 基本数据类型

基本数据类型又分为数值类型和非数值类型。总共有8种;

数值类型
byte (1字节)
short(2字节)
int(4字节)
long(8字节)
float(4字节)
double(8字节)

非数值类型
char(2字节)
boolean(不确定)

2.引用数据类型

引用数据类型就是指,凡是指向堆中对象的引用变量,就是引用数据类型。如 Book book = new Book(); book就是一个引用数据类型。

3. 类型转换

首先类分析一下强制类型转换,所谓强制类型转换,强:强制,强行的意思。
自己的理解就是,大空间字节转小空间字节。如byte变量声明时,会分配1个字节,int型变量会分配4个字节。如果让int转为byte,就是强制类型转换。同样的道理,long转为int,也是大空间转小空间行为。
强制类型转换容易造成数据溢出,所谓溢出。我们可以把8种数据类型比喻为8个不同大小的桶。

当大水桶往小水桶倒水时,(图片虽然有点丑,图糙理不糙
不管小水桶是否能装下,称为强制类型转换(显示类型转换)
反之小水桶的水倒入大水桶,肯定是能装得下。称为隐式类型转换;

基本概念和类型转换道理大概就是上面的说的样子。下面具体来研究java中的实现细节。


一、基本数据类型

1. 整型

	数值型有6种:
	byte (1字节)   		short(2字节)       int(4字节)
	long(8字节)    	float(4字节)       double(8字节)
(1)强制类型转换

下面会降到内存的原码、反码、补码的计算,所以开头先强调几个基本知识点。

1. 内存的数据都是以补码形式存在的;
2. 内存中一个数据由(符号位+真值)表示。最高位表示符号位,0位正,1为负。
4. 正数的原码 = 反码 = 补码;负数计算原码、反码、补码时,最高位的符号位不在计算之中。
3. 移位操作中,右移时,正数的高位不足,用0填补,负数的高位不足,用1填补;左移时,低位不足,都用0填补

实例演示

int i = 200;
byte b = (byte) i;
System.out.println(b);
结果:-56;

我们来分析一下这个例子的过程。这个例子是一个int转为byte类型的强制类型转换。根据上面的基本知识我们知道int类型会分配4个字节,byte类型会分配1个字节。那么内部是怎么计算的呢?

  • 解:
    变量 i 会被分配4个字节,即32位。我们可以想象成容纳量为32的水桶。

    数值 200 会被编译为二进制11001000;我们可以看成是水的体积。其值为正,所以最高位为0;
    那么这个数值的原码表示为:
    0000 0000 0000 0000 0000 0000 1100 1000 (共计32位)
    由于内存中是储存的补码,因此根据计算规则 正数的原码 = 反码 = 补码;
    数值200 的补码为:
    0000 0000 0000 0000 0000 0000 1100 1000 (共计32位)
    所以应该把补码放入32个空格子里。然后来分析变量b;

    变量 b 会被分配一个字节,即8位,相当于容纳量为8的水桶。

    现在要用容纳量为8的水桶盛装该200的数值,即int类型的200值,即上面32位的内存的补码;
    由于该补码有32位长度需要32个空格子,而byte只有8个格子,只能盛装8位,因此会溢出24位。
    溢出规则为高位溢出。只能装下低8位。
    所以实际上,变量 b 存放的数值补码为 1100 1000;

    通过补码观察最高位为1,所以该数为负数。但具体的值要根据计算原码来判断。
    由 反码 = 补码 - 1;可得反码:
    1 100 0111(减法不要做错了哦,我刚刚就算错了)
    由原码 = 反码取反 可得原码:
    1 011 1000
    最高位为1,也是就最左边的第一位,符号为负。
    真值为011 1000,其结果转换为十进制是56;
    因此 最终结果为 -56;

实例演示

int = -40000;
short s = (short ) t;
System.out.println(s);
结果:保密;

先来根据上面的方法计算一下。short的数据类型是2个字节,即16位。除去1个符号位,剩余15个位置可以存储数值。所以short的数值范围在 -2的15次方 ~ +2的15次方;也就是 -32767 ~ 32767。
如果要把 int 型的-40000数值存放到short里面,-40000 < -32767 。所以肯定是会溢出的。其结果我们来算一下。

变量 t 为 int 型将会分配32个空格子,最高位为负,-40000的二进制表示为1001 1100 0100 0000;
所以其变量 t 的
内存原码为:1 000 0000 0000 0000 1001 1100 0100 0000;
内存反码为:1 111 1111 1111 1111 0110 0011 1011 1111 ;
内存补码为:1 111 1111 1111 1111 0110 0011 1100 0000;

short现在只有16个格子,所以装得下的补码为:0110 0011 1100 0000;
最高位为0,所以值为正。也因此,正数的补码=原码,所以该数的原码为:0110 0011 1100 0000;
转化为十进制是25536
所以最终结果为正的 25536
结论:当大水桶中实际的水超过小水桶容量时,强行盛装造成溢出。其值改变。

那如果大水桶中实际装的水,小水桶能盛装得下,会是什么结果呢?

实例演示

short s= 89;
byte b = (byte) s;
System.out.println(b);
结果:保密;

同样的,安装上面的计算方法,我们来计算一下。
首先给short类型的 s 变量分配16个格子,最高位装符号,剩下15位装数值。
89的二进制位: 1011001
所以变量s的内存中的补码为:0000 0000 0101 1001
byte类型的变量 b 只有8个格子,所以它盛装的补码为:0101 1001
通过补码观测最高位为0,其值为正。正数的补码=原码;所以原码也是 0101 1001,转换为十进制是89。
结论:当大水桶中实际的水不超过小水桶容量时,其值不变。

(2)隐式类型转换

其原理和强制类型转换一致,唯一不同的是,一个是大水桶装入小水桶,一个是小水桶倒入大水桶,大水桶肯定能装得下呀,除非大水桶有个洞。
所以不论倒入哪个桶,其值都不会改变,都是一样的。计算方法和上面一致。
还是来演示一遍:

实例演示

byte b= -89;
short s = b;
System.out.println(s);
结果:保密;

首先-89的原码为: 1101 1001;
其反码为:1010 0110;
所以装入 b 的补码为 1010 0111;现在要将-89装入变量 s ,
short 类型的变量 s 有16个格子,将 变量b 中的值装入 s 中后,
其原码为:1 000 0000 1101 1001
其补码为:1 111 1111 1110 0111
这里我也去思考过,为什么变量b的补码不是应该直接装入变量 s 的格子中,变为
0 000 0000 1010 0111 呢?而是变成了
1 000 0000 0010 0111,为什么符号位迁移了?
我也百思不得其解。不过我们可以通过移位运算来验证一下,它的补码到底是什么。
如果是0 000 0000 1010 0111,其最高位为0,这个值肯定为正,
真值的补码也变为了1010 0111。所以这里面一定有些不知道的底层原理。不过可以肯定的是,隐式类型转换其值肯定不会改变的。

(3)混合运算中的类型转换

实例演示

short s = 1;
int  i =130;
byte g = (byte )(s+i);
System.out.println(g);
结果:-125

这里需要注意的是,java中默认数据类型是int,如果用其他类型去接收都要进行类型转换,只不过有些是隐式类型转换就没显示出来。
如: long n = 130; 其实等同于 long n = (long) 130;
这里130是int 类型。
因此,我们来解析一下上面的例子,前两句都是定义,重点在最后一句;

byte  g = (byte )(s+i);

这里 s 是short,i 是 int,s+i 后会默认转为int,因为java的默认数据类型是int。
所以 s + i 后的数值为:int 型的131,两边数值类型不匹配,编译会报错。
可能你会提出这样的疑问:

byte g = (byte)s+(byte)i;

这样写能不能行呢,这样写后其值还是会被默认转为 int型 的131.
所以只能这样写:

byte g = (byte )(s+i);

首先 s+i 后变为 int 型的131,然后再由int强转为byte。
实例演示

byte b = 1;
b = b +1;
System.out.println(b);
结果:报错,b+1后变为int类型,不能直接转为byte。

byte b = 1;
b += 1;
System.out.println(b);
结果:2      += 除了可以实现增加,还会执行强转。因此已经由int转为了byte。

实例演示

	short s = 1;
    int i =2147483647;
    long t = s+i;
    System.out.println(t);
    结果:-2147483648,那为什么不是2147483648?

int的数值类型为-2^31 到2^31,即 -2147483648 到 2147483647。这里结果为什么会是-2147483648。我们来推演一下。
推演之初,我得提出一个问题,下列两个int类型的原码的值代表十进制哪两个数。

1000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 

看起来像 正0 和负 0 吗?其实不是,
其中,
1000 0000 0000 0000 0000 0000 0000 0000 代表负数范围的最小值,即-2147483648。
0000 0000 0000 0000 0000 0000 0000 0000 代表正数范围的最小值,即0。
我们可以推演一下:

2147483648的二进制原码:1000 0000 0000 0000 0000 0000 0000 0000
正数的原码= 反码  1000 0000 0000 0000 0000 0000 0000 0000,在int类中,这个补码表示一个负数,
所以其值为-2147483648。
这也是为什么计算机发展过程中,为什么引入了补码的原因,
就是为了解决正0 和负0 的表达。所以规定了  补码=原码取反+1  的公式,只有这样才能表达出所有数字。

回到上面的实例,int i = 2147483647;
其原码=补码=0111 1111 1111 1111 1111 1111 1111 1111
此补码+1=1000 0000 0000 0000 0000 0000 0000 0000
上下相加,即2147483647 + (-2147483648)= -1,补码:
1111 1111 1111 1111 1111 1111 1111 1111
-1+1,变为1 0000 0000 0000 0000 0000 0000 0000 0000,但是int只能盛装32位,所以高位溢出剩下补码为
0000 0000 0000 0000 0000 0000 0000 0000
也就是十进制的0.

2. 浮点型

浮点型有两种,float(单精度) 和 double(双精度)。浮点在计算机中采用的是科学计数法,对于无限小数,其占位越长,精度越高。但综合使用范围和计算机成本,不同的语言有不同的设定。在java中
float 占 4个字节,共32位内存空间。
double 占 8个字节,共64位内存空间。
java 中的小数 默认为double类型,如果需要转为float,称为强制类型类型转换,需要在其后加f 或者 在前加float。

float h = 1.0;     //结果 编译错误
float f = 1.0f;    // 结果  1.0
double d = 1.0;    //结果 1.0

(1)浮点之间的类型转换

浮点由于有小数点的存在,并且不同小数的小数点在固定32位或者64位空间中的位置不同。按照数学四则运算法则,加减小数之前,需要对齐小数点。因此,为了使小数对齐,不同小数之间的加减运算必然会有一个小数在向左或右移动,移动会导致数据溢出,其结果自然发生变化,也就是精度丢失。这就是不用浮点类型做小数之间运算的原因。
比如:1.2 + 1.112 = ?; 正常数学运算应该等于 2.312 , 但在计算机中,其结果是

结果: 2.3120000000000003

因为double是双精度类型,float是单精度类型,double 转为 float时,对于精度较长的小数, 必然会造成精度丢失,属于强制类型转换,反之为隐式类型转换。其变化细节这里不再研究。

3. 整型与浮点之间的类型转换

实例演示
int  t = 1.234;
system.out.print(t)
// 结果: 1

.1.234 是 double类型,转为整型后,只能保留其原值的整数部分。

实例演示
double  d = 1;
system.out.print(d);
// 结果: 1.0

整型转为浮点后变成了浮点,其后为保留一位的小数。

实例演示
当整型 与 浮点型进行加减运算时的类型变化;
float a = 1 + 1.2f;    // 结果: 2.2  
double b = 1 + 1.2;    // 结果: 2.2
int t= (int)(1+1.2f)    

二、非数值型

1、字符型

字符型就是指基本数据类型char,由于char是采用unicode编码,unicode可以理解为一本很厚的字典。

历史:
	计算机发明之初,美国人的只需要128个字符,即ASCII码,ASCII码是一本很薄很基础字典,
美国人用它就可以表示它的所有需求。后来随着计算机的普及,有些国家所需要表达的字符个数远远超过了128个,并且
不同国家有不同国家的字符。于是美国人又在ASCII码的基础上开始扩展,将所有国家的所有符号用全部统一起来做成了
一本厚字典。也就是Unicode码,它是采用16进制表示。那Unicode 和 UTF-8、UTF-16、UTF-32有什么关系呢?
	上述三种UTF-8、UTF-16、UTF-32是实现Unicode 转换为二进制所采用的的方式,因为现在使用了Unicode这个
字典,如果采用定长字节来表示,最多需要使用4个字节来表示,然后美国人或者有些国家用不着那么长的空间,
一个英文文本明明只需要1kB,结果现在变成了4kB。造成了极大的空间浪费。于是UTF-8就是不定长的编码方式,只需要
一个字节表示的字符,就只开辟一个字节空间,如果需要两个字节,则开辟两个字节空间。所以优秀的 UTF-8 的编码
方式被广泛应用于计算机领域。

char 占用两个字节,可以储存65535个字符。如果超过这个界限,就会造成溢出。举个例子

int a = 65 + 0;
char c = (char) a;
System.out.println( c );
结果:A

Unicode 是在ASCII码的基础上扩展的,所以前128位还是和ASCII码一样,我们这里为了方便说明采用128位前的数据举例子。因此,我们可以计算得知,
数值65的原码为0000 0000 0000 0000 0000 0000 0100 0001,补码也是此码;
char 可以装下16个二进制,所以char内存中的补码实际为0000 0000 0100 0001,该码对照ASCII解析,字符为 A 。

如果
int a = 65 + 65534;
char c = (char) a;
System.out.println( c );
结果: ?

结果应该等于多少呢,我们计算一下。
int型的 a = 65599, 原码= 补码= 0000 0000 0000 0001 0000 0000 0011 1111;
char占2个字节,得到该补码的低十六位,所以它内存的实际补码为
0000 0000 0011 1111
其值为正,所以原码也是 0000 0000 0011 1111。通过ASCII码对照这个是字符 ?.

如果
int b = '中';
System.out.println(b);
结果:20013   

这里以中国的‘中’字来举例子,这句代码实际上是隐式类型转换,因为char是2个字节,int是4个字节,所以char转int 为隐式类型转换。我们可以查一下百度:

可以看到中字的二进制码为:
1110 0100 1011 1000 1010 1101
中字占了三个字节,为什么char能存下呢,因为char存入的十六进制表示的Unicode的码点,
我们查一下中字的Unicode 码点是多少。

我们查到,中字的码点是\u4e2d,\u,代表unsigned,无符号的意思。十六进制为4e2d。
十六进制的4e2d转为二进制是:
0100 1110 0010 1101
所以,char中存储的中字的实际原码是 0100 1110 0010 1101,而不是上面查到的占用了三个字节的二进制码。
现在将char转换为int数字,其原码为:
0000 0000 0000 0100 1110 0010 1101。
转换后, 输出的十进制结果就是:20013

三、布尔型——类型转换

布尔类型不能和其他基本数据类型相互转换,而且也不能使用位运算什么的。所以放弃研究了。

四、引用型——类型转换

引用数据类型,就是除去基本数据类型外的其他所有类型的引用变量。
在内存中,引用变量也是一个数据,只不过其存储的值是一个地址,这个地址指向了这个变量真正的值。所以将引用变量称为引用数据类型。
那么引用数据类型既然存储的是一个地址,那么就有几种情况来分析了。

(1)这个引用指向空对象

int a = null;
编译错误

a这个变量是一个基本数据类型,基本数据类型不能接受null对象,所以编译出错。

Integer g = null;

g 这个变量是一个对象引用,null可以强转为任何对象。因为对象是可以接受null的。

(2)这个引用指向一个其他对象

实例演示 : 非父子关系

	Student  a = new Student;
	Teacher t = (Teacher) a;

这里,要把Student转为Teacher对象。显然由于类型不匹配,编译器都通不过,因此编译会报错。

实例演示 : 父子关系

Object ob = new Object();
Teacher t = (Teacher)ob;   父转为子(强制转换)也称为向下转型
Object  et = Teacher ;  子 转为 父(隐式转换)也称为向上转型

Object 是万事万物的根源,犹如宇宙的太极。太极生两仪,两仪生四象,四象生八卦。天地万物都是由Object衍生出来的。所以它是所有对象的父类。
这里的转换出现了两个词语。一个是向上转型,一个是向下转型。为什么向下转型称为强转,而不是向上转型呢?
前面基本数据类型对于强转的定义为,大空间转入小空间,造成数据溢出,具有数据出错的风险性。这里也是类似的道理。
在小猿的理解中,变量是一个值,其值指向堆中的对象。声明变量的类型就是堆中对象的映射。请看图:

一个 对象具有它自己的属性和行为方法。对于父类而言,子类是在父类的属性和行为方法上的扩展。那么同样的,在声明Teacher类的变量 t 时,默认说明变量 t 将指向一个 Teacher 的实例对象,现在却要把这个指向改变为父类对象Object。

这里就会涉及到数据出错的风险。如果变量 t 调用的所有方法中,Object全都有,调用它所有方法时全都能成功,那么Teacher 类型的变量 t 就不会有数据出错的风险,这个父子类型的强制类型转换是安全的。反之,如果Object 只有 变量 t 方法中的部分方法,那么调用Teahcer 对象有,而Object对象 不具有的方法时,就会出错。

开心一刻

小王是工程局的一名干事,有一天领导交给他一个工程让他去负责办理,三个月后验收。
小王心里高兴得不了,这项工程实际上一个月就可以完成了,于是他开始想剩下的两个月怎么办。
如果早早把工程就办完了,那就得继续干下个工程,即得不到多余的报酬,也浪费了这两个月假期。
于是小王带着一家人去了国外旅游,计划好时间。到时候回来开工。
谁曾想,返回的途中遇到了意料之外的麻烦,匆匆赶回国后只剩下15天时间了。但是还有领导交给他
的一个月工程没干。无奈之下,小王只有昼夜加班,只干完了主体工程。
...
到了交接那天,领导拿着图纸来查看工程进度。发现跟图纸不匹配,工程主体缺这儿缺那儿的,领导一肚子火气。
于是小王遭到了惩罚,被领导炒了鱿鱼。
跟上述对象强制转换一样。引用变量 t 原以为可以调用Teacher 类型的所有方法,结果中途被小王换成了
对象Object ,所以在调用某些方法时,程序就报错了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值