详解数据类型
数据类型
在Java中,数据类型主要被分为两类:基本数据类型和引用数据类型
那么本章中主要介绍的是基本数据类型
基本数据类型
数据类型 | 关键字 | 内存占用(字节) | 数值范围 |
---|---|---|---|
字节 | byte | 1 | -128 ~ 127((-2^7) ~ (2^7)-1) |
短整型 | short | 2 | (-2^15) ~ (2^15)-1 |
整型 | int | 4 | (-2^31) ~ (2^31)-1 |
长整型 | long | 8 | (-2^63) ~ (2^63)-1 |
单精度浮点数 | float | 4 | 有范围,一般不关注 |
双精度浮点数 | double | 8 | 有范围,一般不关注 |
字符型 | char | 2 | (-2^15) ~ (2^15)-1 |
布尔类型 | boolean | 没有明确规定 | true 和 false |
其中数值范围我们只用知道byte
和boolean
的即可,其他的看看了解了解就行
关于内存单位的转换(后面的都是乘1024就不写了):
- 1byte=8bit
- 1KB=1024byte
- 1M=1024KB
- 1G=1024M
在文章的末尾会介绍整型和浮点型在内存中的存储,可以选择性观看
字符串
在Java中使用String类定义字符串类型,比如:
public class Test {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
}
}
注意和字符char
区分,字符是用单引号括起来的,字符串String
是用双引号括起来的
字符串的拼接
我们在输出的时候,可能需要和变量之类的结合,或者是多段字符串一起输出,那么这个时候就要用+
才能进行拼接
public class Test {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
System.out.println(s1+" "+s2);
//输出hello world
}
}
假如涉及到变量(如果不懂变量可以先看文章后面)
public class Test {
public static void main(String[] args) {
int a = 0;
System.out.println("a = " + a);
}
}
那么我们看一下下面的语句
public class Test {
public static void main(String[] args) {
int a = 5;
int b = 10;
System.out.println("hehe" + a + b);
System.out.println(a + b + "hehe");
//输出 hehe510 15hehe
}
}
变量值和字符串拼接的时候,会自动转换并合并为一个字符串,所以第一个输出的是hehe510
第二个语句由于整型计算在前面,先相加为了30
才进行字符串的拼接,所以输出的是15hehe
但是倘若我们就想输出hehe15
怎么办呢?那就加上括号即可
public class Test {
public static void main(String[] args) {
int a = 5;
int b = 10;
System.out.println("hehe" + (a + b);
//输出hehe15
}
}
常量
常量,顾名思义即无法改变的量,例如我们下面的输出语句输出的就是一些常量
public class Test {
public static void main(String[] args) {
System.out.println("abc");
System.out.println(1);
System.out.println(3.14);
System.out.println('a');
System.out.println(true);
}
}
那么一般来说,每个数据类型都会有其对应的常量。但是有一种常量没有类型,称作空常量null
,我们将来在后面的学习中再对它进行说明
变量
变量的定义
变量,指可以改变的量,和常量类似,每一种数据类型都有其对应的变量,那么我们要如何定义一个变量呢?
意义变量的语法如下
数据类型 变量名称 = 变量初始值;
//例如
int a = 0;
double b = 0.0;
char c = 'c';
boolean d = true;
在之前,我们输出类似于HelloWorld这样的字符串的时候,是要加上双引号的""
,而倘若我们想要输出变量的内容,则直接将变量放到输出语句里就行
public class Test {
public static void main(String[] args) {
int a = 0;
double b = 0.0;
char c = 'c';
boolean d = true;
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
那么我们要怎么样改变一个变量呢?实际上我们只要将一个常量直接赋值给变量就行
public class Test {
public static void main(String[] args) {
int a = 0;
a = 10;
//改变的时候也可以赋值含自身的表达式
int b = 10;
b = b + 1;
}
}
同时Java也支持一次性定义多个变量
public class Test {
public static void main(String[] args) {
int a1 = 10, a2 = 20, a3 = 30;
}
}
变量也可以在刚开始创建的时候不赋予初始值,在后面使用的时候再赋予初始值。但是Java中如果变量不赋予初值无法调用,所以建议创建的时候就直接初始化,没东西赋予就赋0
public class Test {
public static void main(String[] args) {
int b;
b = 10;
System.out.println(b);
//下面这种方式是会报错的
int c;
System.out.println(c);
c = 100;
}
}
按照作用域划分的变量类型
类变量
从属于类的变量,在整个类里可以使用
public class test {
static double a = 1.101;
//这样声明和设定初始值后 下面可以直接使用
public static void main(String[] args) {
}
}
局部变量
在一个局部范围内起作用的变量
public class test {
public static void main(String[] args) {
int i = 10;
//这个变量只能在main方法内部使用
}
}
实例变量
目前先不说明,后面学习到类和对象的时候了解
一些变量的注意点
Long类型变量和Float类型变量的书写
在写long
类型变量和float
类型变量的时候,需要注意一定格式
public class Test {
public static void main(String[] args) {
//long类型变量赋值时值后面要加L,大小写都可以但是推荐大写(怕和1弄混)
long a = 100L;
//float类型变量赋值时值后面要加f,大小写都可以
float b = 1.1f;
}
}
那么为什么要这样写呢?实际上,Java中赋值的时候,默认整型为int
,浮点数为double
对于整型来说,long
的范围是大于int
的,假设我们要将一个超出int
范围的值赋予给一个long
变量。如果不加标记,那么这个数字就被解析为int
,那么就会报错,因为超出了int
的表示范围
对于浮点型来说,float
的精度是小于double
的,那么假设我们将一个double
赋值给float,那么就会发生精度的丢失。如果没有加上标记,那么这个时候Java就会报错,提示你有精度丢失,而你此时加上一个f
,就相当于是进行强制类型转换直接舍弃那些精度
但实际上这里涉及到类型转换,还是比较复杂的,在文章的后面一同说明
除法的运算
当我们运行下面代码的时候,想要输出一个0.5
的时候,会发现真实输出的结果和我们需求的有所差异
public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println(a/b);
//输出0
}
}
实际上,假如我们要在除法中算出小数,那就只能使用浮点数运算,否则算的结果就和小学舍去余数的结果一样
那么是为什么呢?实际上这涉及到数据在内存中的存储,但是也可以简单理解为:整型在内存中的存储方式和浮点数不同,没有存放小数位的地方,因此整型运算的时候会直接舍弃小数位
那么假如说我们要算出小数位,那要怎么做呢?
public class Test {
public static void main(String[] args) {
//方法一:用浮点数运算
double a = 5;
double b = 11;
System.out.println(a / b);
//方法二:计算时进行强制类型转换
int c = 1;
int d = 2;
System.out.println((double) c / d);
}
}
对于方法二,有必要说明的是,只要在表达式中有一个浮点类型,那么整型就会进行隐式类型转换,提升为浮点类型再进行计算
关于隐式类型转换,文章后面会讲到
浮点数的不精确性
我们先看一下下面的代码
public class Test {
public static void main(String[] args) {
double num = 1.1;
System.out.println(num * num);
}
}
会发现输出的结果是1.2100000000000002
,那么又是为什么呢?
这里实际上涉及到浮点数在内存中的存储,但是可以简单理解为:由于浮点数在内存中的存储规则,有一些浮点数在内存中是无法精确存储的,只能存储近似值
布尔型变量的独立性
布尔类型用于帮助判断真假,在Java中判断真假的表达式必须全部为布尔类型,而不能用非0真,0假
的判断方法
并且布尔类型不能进行类型转换,例如
public class Test {
public static void main(String[] args) {
boolean value = true;
//这样写是会报错的,相当于将boolean强转为int
System.out.println(value + 1);
}
}
类型转换
上面我们多次提到了类型转换的概念,那么类型转换到底是什么意思呢?
实际上,由于Java是强类型语言,因此对于数据类型的检查十分严苛,因此假如数据的类型不同,就要发生类型转换,类型转换的类型主要分为两类:隐式类型转换和强制类型转换(也可以叫做自动类型转换和显式类型转换)
隐式类型转换
隐式类型转换会发生在我们用数据范围较小类型赋值给大类型的时候
例如下面的两个赋值就发生了隐式类型转换
public class Test {
public static void main(String[] args) {
int a = 1000;
double b = a;
float c = 1.1f;
double d = c;
}
}
但是对于整型的字面常量赋值,有一些特例
我们上面也说过,实际上Java在我们书写整型的时候,默认其类型为int
,当我们用整型字面常量直接赋值给byte
和short
类型的时候,只要字面常量的大小不超过这两个类型能存储的大小,就不会报错
public class Test {
public static void main(String[] args) {
byte a = 100;
short b = 1000;
//上面这些是不会报错的写法
int c = 100;
//下面这两个是会报错的,因为不是字面常量赋值
byte d = c;
short e = c;
//下面这个也会报错,因为超过了byte的范围
byte f = 10000;
}
}
隐式类型转换还会发生在不同数据类型进行运算的时候,精度低的数据会自动被提升到高精度的数据
public class Test {
public static void main(String[] args) {
float a = 1.1f;
int b = 2;
float c = b/a;
//b就会自动转换为float然后在进行运算
}
}
隐式类型转换还会发生在低精度的整型运算中
首先,我们需要知道Java的整型算术运算总是至少以int的精度来进行的,所以当精度低于int的数据类型进行计算的时候(比如byte、short),就要将精度提升到 int类型的精度,那么这个转换的过程就被称作整型提升
由于整型提升的存在,我们在进行低精度整型运算的时候,可能要进行强制类型转换
public class Test {
public static void main(String[] args) {
byte a = 1;
byte b = 2;
//正确写法
byte c = (byte)(a+b);
//下面的写法会报错,会提示你(a + b)是int类型
byte d = a+b;
}
}
那么为什么要进行整型提升呢?原因是:一是一般CPU的整型计算器最小操作字节长度就是int
类型的字节长度,也就是4个字节。若为低精度计算则剩余位置空着,可以说是不用白不用,并且这样方便了硬件上的实现
强制类型转换
当你想要进行一些运算的时候,发现由于类型无法隐式自动转换报错了,那么这个时候就可以用强制类型转换来转换我们的数据类型
强制类型转换的格式
(要转换成的类型)要转换的数据
public class Test {
public static void main(String[] args) {
long a = 100L;
//不进行转换就会报错
int b = (int)a;
}
}
但是强制类型转换有几个注意点:
- 使用强制类型转换即默认可以发生精度和数据的丢失,使用的时候一定要注意
boolean
类型不能进行强制类型转换
数据在内存中的存储
本部分内容可能较难理解,可以选择性阅读
整型
整型家族一般有下面几个类型
byte
short
int
long
char
//Java中字符本质为Unicode码值,也可以被看作是整型
数据在内存中是以二进制的方式表达的,其中内存占用的大小决定了它们有多少个二进制位,1个bit位代表一个二进制位。
整型数据在内存中的二进制表达有一些规定:
- 最高位为符号位,1表示负号,0表示正号
- 二进制有三种表示方式,原码、反码、补码,其中内存中存储的是补码
- 正数的原码反码补码都是相同的,所以直接用二进制表达就可以表示正数在内存中的存储
- 负数中,原码是直接用二进制表达,反码是原码除符号位取反,补码是反码加1
- 原码和补码的互相转换方法都是,除符号位取反后加1
那么为什么要在内存中存放补码?这里列举两个优点
1.由于
cpu
只能进行加法运算,补码在相加时可无视符号位直接进行正常二进制相加2.原码补码相互转换的方法相同:除符号位取反后加一
那么以byte
为例子说明一下整型在内存中的存储
byte
有8个二进制位,首位为符号位
以下都为**byte
在内存中的补码表示**
000000000
000000011
…(+1) x N
01111111 这个时候为byte
能表达的最大正数127
,但倘若我们再+1
10000000 就变成了-128,这里注意:byte
直接认定补码符号位为1其余全为0的为-128
10000001-127
…(+1) x N
11111111-1
此时若还+1
00000000 那就回归到了开始的0
因此byte
的取值范围是 -128~127
用一张图片来表示就是
浮点型
浮点型家族一般就指下面两个类型
float
double
总所周知,数据在内存中均以二进制方式存储,那么知道浮点类型的二进制表达方式就是我们研究其在内存中是如何存储的核心
实际上也很简单,与十进制也是类似的。在十进制中,0.1是10的-1次方,0.01是10的-2次方
那么二进制的也是如此0.1就是2的-1次方(也就是10进制的0.5),0.01就是2的-2次方(就是10进制的0.25),后面都以此类推
(实际上就是一直除以2)
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数N可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,N为正数;当S=1,N为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位
S就是符号位,0正1负
M其实就类似于十进制中科学计数法的底数(十进制标准科学计数法中,底数要大于等于1小于10),只不过这里M是二进制科学计数法的有效数字
E实际上就是类似于十进制科学计数法中10的n次方中的n,那么在这里就表示为2的E次方
举例来说,5.5表达成二进制就是101.1,用科学计数法表示就是1.011 x 2^2 ,那么其中S就是0,M为1.011,E为2
那么可能有些对于数字比较敏感的人就会意识到,那这样是不是有一些浮点数无法精确表示?
答案是,确实如此。因为我们可以发现,浮点数在二进制中的表达是类似于0.5 0.25 0.125 0.0625.....
这样的等比数列,并不是和整型一样每一个数字都可以被直接表示。所以浮点数即使能成功表达,也是经过上面等比数列中的数凑出来的,当凑不出来的时候,就会取近似值。
有关M的存储
上面我们了解到,M都是x.xxxxx
的形式,那可能有人想,这个不也是小数吗?那为什么存储的时候还要特意化成这种形式,而不是直接存呢?
但是由于标准中规定了M必须大于等于1,小于2,所以实际上M的形式为1.xxxxx
由于第一个位一定是1,那么存储的时候就不会存储这个1,等取出的时候再重新把1加上就行。在存储时,就只存储小数点后面的位,这样不仅方便存储,而且还增加一个位的精度
存放M的时候,优先把xxxx
放入,后面空的位置会自动补0
有关E的存储与取出
首先我们要知道一个规定,E是不会表达负数的,所有二进制位为正常二进制表达,不存在符号位。如果E有8位那表示的就是0~255
,如果有11位那就表示的是0~2047
,但是科学计数法中,指数是允许出现负数的,为了解决这个问题,E在存入内存时,会加上一个中间值(8位为127,11位为1023)
计算方式 真实值 + 中间值 = 内存中存的值
接下来的说明均以单精度浮点型说明,即E只有8位,不然看起来很乱
例如,2^10在内存中存的就是137
关于E的取出又有三种情况
E不全为0或不全为1
如果是这种情况,那就按照正常方式去反推计算即可
E全为0
E全为0,即内存中存储的为0,实际值应该为-127
此时E被直接当成1-127,也就是-126,由于2^-126是可以被说成无限趋近于0的数,所以在计算时,M原本要添加的1也不再添加,直接变为0.xxxxxxx
E全为1
上面说E全为0是趋近于0的数
那么当全为1的时候2^127就可以说成是无穷大了,这个时候只看符号位是正还是负即可,用于区别是正无穷还是负无穷