上一篇,简单概括了Java的基本数据类型,在Java的世界中一切皆为对象,针对八种基本类型,也提供了对应的对象类型,即基本数据类型的包装类型,如下所示的对应表:
基本类型 | 包装类型 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
来到这里,可以看出Java世界中的两大数据类型:基本数据类型,引用数据类型。
基本数据类型:就是Java的八种基本数据类型,而引用数据类型指代Java中的对象类型,它们在堆中分配内存,我们使用的只是它们地址的引用。具体如下表所示,该表参考自https://blog.csdn.net/zhangsanfeng2009/article/details/80900963:
基本数据类型 | 引用数据类型 |
在栈中进行分配 | 在堆中进行分配,堆的读写速度远不及栈 |
变量名指向具体的数值 | 变量名指向存数据对象的内存地址,即变量名指向对象在堆中分配的地址值 |
变量在声明之后java就会立刻分配给他内存空间 | 它以特殊的方式(类似C指针)指向对象实体(具体的值),这类变量声明时不会分配内存,只是存储了一个内存地址 |
基本类型之间的赋值是创建新的拷贝 | 对象之间的赋值只是传递引用 |
使用“==”和“!=”比较两个基本类型的值 | “==”和“!=”是在比较两个对象的引用(堆内地址)是否相同,如果要比较内容是否相同,需要自己实现equals()方法 |
基本类型变量创建和销毁很快 | 类对象需要JVM去销毁 |
现在,反过来思考,既然有了包装类型,并且能够满足使用的需要,Java作为一个完全面向对象的语言,为什么还要保留基本数据类型呢?这里引用《Thinking in Java》中的一段内容:在程序设计中经常用到一系列类型,它们需要特殊对待。可以把它们想像成“基本”类型。之所以特殊对待,是因为new将对象存储在“堆”里,故用new创建一个对象——特别是小的、简单的变量,往往不是很有效。因此,对于这些类型,Java采取与C和C++相同的方法。也就是说,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。
同时要注意Java中每种基本类型所占存储空间的大小是确定的,即它们的大小并不像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是Java程序比用其他大多数语言编写的程序更具有可移植性的原因之一。
虽然java中基本数据类型和它们对应的包装数据类型,是两种完全不同的类型,但是在操作上,二者却可以自动进行转换,这里就不得不提Java1.5之后引入的自动装箱和拆箱操作。
自动装箱:将基本类型用它们对应的引用类型包装起来。
自动拆箱:将包装类型转换为基本数据类型。
public class DateType {
int anInt = 10;
Integer integer1 = anInt;//自动装箱
Integer integer = 20;//自动装箱
int anInt1 = integer; //自动拆箱
//得益于这种机制,实际使用过程中更加方便
}
自动拆装箱很好用,那么它的原理是什么呢?
通过反编译的手段,我们可以了解自动拆装箱的具体原理:
如我们有如下自动拆装箱代码:
public static void main(String[]args){
Integer integer=1; //装箱
int i=integer; //拆箱
}
对这段代码编译后,进行反编译,可以得到如下代码:
public static void main(String[]args){
Integer integer=Integer.valueOf(1);
int i=integer.intValue();
}
从上面反编译后的代码可以看出,int的自动装箱都是通过Integer.valueOf()
方法来实现的,Integer的自动拆箱都是通过Integer.intValue
来实现的。可以试着将八种类型都反编译一遍 ,就会发现以下规律:自动装箱都是通过包装类的valueOf()
方法来实现的.自动拆箱都是通过包装类对象的xxxValue()
来实现的。参考自:https://blog.csdn.net/wufaliang003/article/details/82347077
再来说另外一个和自动拆装相关的有趣的事情,即缓存,先看如下代码的运行结果:
public class DateType {
public static void main(String[] args) {
Integer a = 20;
Integer b = 20;
Integer c = new Integer(20);
Integer d = 300;
Integer e = 300;
System.out.println(a == b);//结果为:true
System.out.println(a == c);//结果为:false
System.out.println(d == e);//结果为:false
}
}
首先,对于引用类型,“==”判断的是二者在堆中分配的内存地址是否相同,通过以上的对以上运行结果的解释,我们可以看到一个包装类中很有趣的现象,以Integer为例,原因就和Integer中的缓存机制有关。在Java 5以后,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。但缓存适用于整数值区间-128 至 +127。而且只适用于自动装箱。使用构造函数new对象不适用。其中最大值127是可以自己设定的。可以通过查看Integer源码来获得更详细的说明:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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;
}
通过对源码的解读,大致可以总结如下:Integer是对小数据(-128~127)是有缓存的,缓存在第一次使用时初始化,数据-128~127之间的数字便被缓存到了本地内存中,如果初始化-128~127之间的数字,会直接从内存中取出,不需要新建一个对象。
介绍完包装类型,关于Java中常用的数据类型基本上就包括完了,这里还有两个比较重要的引用数据类型,就是Java提供的用于高精度计算的类:BigInteger和BigDecimal。虽然它们大体上属于“包装器类”的范畴,但二者都没有对应的基本类型。
BigInteger支持任意精度的整数。也就是说,在运算中,可以准确的表示任何大小的整数值,而不会丢失任何信息。
BigDecimal支持任意精度的定点数(小数),例如:可以用它进行精确的货币计算(货币计算中,要使用String类型的构造函数)。