[toc]
数据类型的出现是为了把数据分成所需内存大小不同的数据,合理、有效利用内存。数据类型在计算机语言里面,是对内存位置的一个抽象表达方式。
数据类型
在 JAVA 语言种将数据类型分为两类:基本数据类型和引用数据类型
基本类型:简单数据类型是不能简化的、内置的数据类型、由编程语言本身定义,它表示了真实的数字、字符和整数。
引用数据类型:Java 语言的引用数据类型包括类、接口和数组类型,还有一种特殊的 null 类型。引用数据类型是对一个对象的引用,对象有两种:数组和实例(面向对象的基本知识点,这篇文章主要以基本数据类型为主)
基本数据类型
基本数据类型分为 4 类,分别为整型、浮点型、字符型和布尔型
整型
1 字节 = 8 位
4 种整型量都是有符号的,符号占一位(byte 的取值范围本该是 2 的 8 次方,其他类似)。
Java 整数常量默认是 int 类型
下表可以清楚的看出每个取值范围内可以使用的数据类型(这里没有把负数部分画出来)
取值范围
0~127
128 ~ 32767
32768 ~ 2147483647
2147483648 ~ 9223372……
数据类型
byte、short、int、long
short、int、long
int、long
long
byte、short 可以使用在其取值范围内的整数常量,系统自动把这些整数常量当成 byte 或 short 类型处理。使用超出范围的整数常量会报错。
byte b = 127;
byte b2 = 128;// 报错:Required:byte Found:int(默认是 int 类型)
如果把一个超出 int 取值范围的整数常量赋值给 long 类型,会报错;强制使用 long 类型需要在整数常量后加 L(或 l)
如果把一个 int 范围内的整数常量赋值给 long 类型,不会报错,是因为发生了自动类型转换(下面会具体说明)
int num1 = 2147483647;
long l1 = 2147483647;// 正常,发生了自动类型转换
int num2 = 2147483648;// 报错: Integer number too
large
long l2 = 2147483648;// 报错: Integer number too
large
long l3 = 2147483648L;// 正常
整数常量的四种表示方式
二进制:0b 或 0B 开头(java7 开始支持)
八进制:0 开头
十进制:不做过多说明
十六进制:0x 或 0X 开头
int num1=013;// 八进制
int num2=0x13;// 十六进制
重点说明二进制表示方式
所有数字在计算机底层都是以二进制形式表示的,原码是直接将一个数值换算成二进制数。计算机是以补码形式保存所有的整数。正数的补码和原码完全相同,负数的补码是原码的反码加 1;反码是对原码按位取反,最高位符号位不变。
二进制整数的最高位是符号位,1 表示负数,0 表示正数
java 整数常量默认是 int 类型,所以二进制整数默认占 32 位,定义一个不足位的二进制整数时,会自动高位补齐(补 0),第 32 位是符号位
系统对 byte、short 类型的使用与十进制一致
使用 long 类型需要在末尾加 L(或 l),表示占 64 位,第 64 位是符号位
关于进制间的转换请看进制间的转换 这篇文章
浮点型
float(单精度浮点型): 第 1 位是符号位,接下来 8 位表示指数,其余 23 位表示尾数
double(双精度浮点型):第 1 位符号位,接下来 11 位表示指数,其余 52 位表示尾数
浮点数的两种表示方式
十进制数形式,float 浮点数后加 F(或 f),double 浮点数后加 D(或 d), 浮点数必须包含一个小数点, 如 float f=5.1200_001f;// 可以使用_分隔,方便校对数位
科学计数法形式,只有浮点型的数值可以使用科学计数法形式表示。如:5.12E2(5.12✖️10^2)
字符型
java 语言使用 16 位的 Unicode 字符集 (关于字符编码问题请看阮一峰的字符编码笔记) 作为编码方式
char 类型的取值范围是 0~2^16(65536), 是无符号的整数(正数), 可以被用作整数类型的值来使用, 同样系统也会把整数类型的值当成 char 类型处理
字符型常量的三种表示方式
直接通过单个字符来指定, 如 ‘a’,’0′
通过转义字符表示特殊字符型常量
常用转义字符
直接使用 Unicode 值表示字符型常量,范围 ’u0000′-‘uFFFF’, 其中前 256 个 (‘u0000’-‘u00FF’) 字符和 ASCII 码表中的字符安完成重合
布尔型
用于表示逻辑上的”真“或”假“
基本类型的类型转换
自动类型转换
取值范围小的类型赋值给取值范围大的类型,系统自动进行类型转换,如图所示,可以根据箭头指向发生自动转换
强制类型转换
强制类型转换可能会丢失精度,所以需要使用 () 进行类型转换。
32 位 int 型最高位 0 表示正数,转换为 byte 类型,高位精度丢失,第 8 位为 byte 最高位,1 表示负数,因此正数的 int 型因丢失精度变成了负数的 byte 型。
表达式类型的自动提升
当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升
byte、short、char 自动提升为 int 类型
整个表达式的数据类型自动提升到与表达式中最高等级操作数的数据类型
与字符串拼接规则:字符串在 + 号的左边,表示字符串拼接, 如果字符串在 + 号的右边要具体看左边的情况
System.out.println(“Hello” + ‘a’ + 7);//Helloa7
System.out.println(‘a’ + 7 + “Hello”);//104Hello
包装类
为什么还需要包装类
Java 中的 int 类型在内存中只占用 4 个字节,而一个 Object 对象本身最少占用 8 个字节,另外还需要 4 个字节来引用它。除此之外 CPU 对基本类型的处理更加高效。
既然如此为什么还需要包装类?
Java 是面向对象的编程语言,但这 8 种基本数据类型并不支持面试对象的特性。所以 Java 需要一个这样的包装类来使其面向对象的完整性, 这样一来基本类型具有了对象的特性,例如:实现可空类型,完成字符串像基本类型的转换,另外 Java 集合中只能放入包装类型,不支持基本类型。
// 可为空
Integer integer=null;
// 字符串转换为 int 型
int num=Integer.valueOf(“123”);
//java 集合只能放入 Integer 类型,无法放入 int 类型
ArrayList<Integer> arrayList=new ArrayList<>();
缓冲池——减少内存开销,提高程序性能
Integer integer = Integer.valueOf(127);
int intValue = integer.intValue();
通过静态方法 valueOf 将基本类型包装为 Integer 类型
通过 intValue 方法将包装类转换为 int 类型。
private final int value;
//intValue 方法比较简单,直接返回 value
public int intValue() {
return value;
}
//valueOf 方法的内部实现
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以从 valueOf 方法内部看到,传入进来的 i 并不是直接调用 new Integer(i)来创建对象的,而是通过 IntegerCache.cache 缓存中获取的。
IntegerCache 是 Integer 的一个内部类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”);
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE – (-low) -1);
} catch(NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high – low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
从上面的代码中,可以看到 IntegerCache 类中声明了 Integer cache[]数组来缓存 Integer 对象的,并且 IntegerCache.low 和 IntegerCache.high 常量确定了 cache 的范围从 -128~127,也就是说不在这个范围内的值,会直接通过 new Integer(i)来创建对象。
Integer num1 = 127;
Integer num2 = 127;
System.out.println(num1 == num2);// 引用的同一个对象所以输出为 true
Integer num1 = 128;
Integer num2 = 128;
System.out.println(num1 == num2);// 超出了缓存范围输出 false
Integer integer1 = new Integer(127);
Integer integer2 = new Integer(127);
System.out.println(integer1 == integer2);// 直接 new 的对象,未走缓存输出 false
在 int 和 Integer 类型之间转换其实不需要如此的麻烦,从 java5 开始为我们提供了装箱和拆箱的操作,简化转换过程。
自动装箱、拆箱——只是一种语法糖
Integer a= 123; // 装箱
int b = a; // 拆箱
装箱和拆箱可以看成一种 java 语法糖,javac 编译器会自动把装箱转换为 Integer.valueOf(), 把拆箱转换为 Integer.intValue(),因此我们在使用装箱时依然可以使用 Integer 的缓存。
成员变量 value 的不可变性
包装类的成员变量 value 都使用了 final 关键字修饰,这表明它们是不可变类型(常量)。
对于成员变量 value 只需要明白 3 点就可以了
它的访问修饰符是 pirvate,所以外部无法修改它
使用了 final 关键字修饰,内部一旦赋值便无法再改变
基本数据类型的不可变性满足上面两点就可以了,不像引用类型,被 final 修改后,引用本身不可变,但具体的实例是可以通过引用改变的。这点在 String 类型中详细说明(String 是通过 copyOf()方法来保证不可变的)
之所以设计成不可变的的目的是为了保证基本的信息安全和并发环境下的线程安全
其他基本类型的包装类
Boolean, 缓存了 true/false 对应实例
Short,缓存范围与 Integer 一致
Byte,全部缓存
Character,缓存范围 ’u0000′ ~ ‘u007F’
Double 和 Float 类型没有使用缓存池
运算
将运算符和操作数连接在一起就形成了表达式
这里只对两种运算做简要说明(其实主要是为了记录下)
逻辑运算符
// 注释
&:逻辑与 &&:短路与
|:逻辑或 ||:短路或
!:逻辑非 ^:逻辑异或
位运算符(直接对二进制进行运算)