但行好事
莫论前程❤

Java基础学习总结–基本数据类型-包装类-引用类型

Java 中预定义了八种基本数据类型,包括:

  • byte,short, int, long 整数类型 数值类型
  • double,float 浮点类型 数值类型
  • boolean 布尔类型
  • char 字符类型
byte  :   8位,  最大存储数据量是255,          数据范围是-128~127之间。
short :  16位,  最大数据存储量是65536,        数据范围是-32768~32767之间。
int   :  32位,  最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
long  :  64位,  最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
float :  32位,  数据范围在3.4e-45~1.4e38,     直接赋值时必须在数字后加上f或F。
double:  64位,  数据范围在4.9e-324~1.8e308,   赋值时可以加d或D也可以不加。
char  :  16位,  存储Unicode码,用单引号赋值。
boolean: 只有true和false两个取值。

​ 我们都说,Java 是一门面向对象型程序设计语言,但是它设计出来的「基本数据类型」仿佛又打破了这一点,所以,只能说 Java 是非 100% 纯度的面向对象程序设计语言。

​ 但是,为什么 Sun 公司一直没有删除「基本数据类型」,而是为它增设了具有面向对象设计思想的「包装类型」呢?

Java引用类型

Java有 5种引用类型(对象类型):

  • 接口
  • 数组
  • 枚举
  • 标注

引用类型:底层结构和基本类型差别较大

JVM的内存空间:

  • Heap 堆空间:分配对象 new Student()
  • Stack 栈空间:临时变量 Student stu
  • Code 代码区 :类的定义,静态资源 Student.class

例:

Student stu = new Student(); //new 在内存的堆空间创建对象
stu.study(); //把对象的地址赋给stu引用变量

上例实现步骤:

  1. JVM加载Student.class 到Code区
  2. new Student()在堆空间分配空间并创建一个Student实例
  3. 将此实例的地址赋值给引用stu, 栈空间

基本类型 VS 对象类型

基本类型与对象类型最大的不同点在于,基本类型基于数值,对象类型基于引用

image

​ 基本类型的变量在栈的局部变量表中直接存储的具体数值,而对象类型的变量则存储的堆中引用。

显然,相对于基本类型的变量来说,对象类型的变量需要占用更多的内存空间

​ 之所以 Java 里没有一刀切了基本类型,就是看在基本类型占用内存空间相对较小,在计算上具有高于对象类型的性能优势,当然缺点也是不言而喻的。

​ 所以一般都是结合两者在不同的场合下适时切换,那么 Java 中提供了哪些「包装类型」来弥补「基本类型」不具备面向对象思想的劣势呢?

基本类型包装类
booleanBoolean
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

可以看到,除了 int 和 char 两者的包装类名变化有些大以外,其余六种基本类型对应的包装类名,都是大写了首字母而已。

缓存池介绍

基本数据类型包装类除了Float和Double之外,其他六个包装类都有常量缓存池 .

​ 拿Integer来举例子,Integer类内部中内置了 256个Integer类型数据,当使用的数据范围在 -128~127之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围则会创建对象。

Integer

测试代码
public static  void main(String args[]){
     Integer i1 = 40;
     Integer i2 = 40;
     Integer i3 = 0;
     Integer i4 = new Integer(40);
     Integer i5 = new Integer(40);
     Integer i6 = new Integer(0);

    //40在常量池中,两个引用地址一样,true
     System.out.println("i1 == i2,"+(i1==i2));

    //数学运算是在栈中进行,此时比较的是数值,true
     System.out.println("i1 == (i2+i3),"+(i1 == i2+i3));

    //i4是新建对象的引用,i1是40的引用,两者不一样,false
     System.out.println("i1 == i4,"+(i1==i4));

    //两个新建对象,false
     System.out.println("i4 == i5,"+(i4==i5));

    //此时比较的是数值,true
     System.out.println("i4 == (i5+i6),"+(i4==i5+i6)); 
}

常见面试题

public static void main(String[] args){
    Integer i1 = 100;
    Integer i2 = 100;
    Integer i3 = 200;
    Integer i4 = 200;

    System.out.println(i1==i2);      // true
    System.out.println(i3==i4);      // false
}

​ 直接将整型数值赋值给 Integer 实例将发生装箱操作,也就是调用 valueOf 方法,而这个方法我们分析过,会首先检查一下 100 是否在缓存池是否缓存了,当然 IntegerCache 会默认缓存 [-128,127] 之间的 Integer 实例,所以这里会直接从缓存池中取出引用赋值给变量 i1 。

​ 同理 i2 也会从缓存池中取引用,并且两者的引用的是同一个堆对象,所以才会输出 「true」。

​ 而第二个输出「false」也是很好理解的,因为 200 不再缓存池缓存的范围内,所以每次调用 valueOf 方法都会新建一个不同的 Integer 实例。

public static void main(String[] args){
    Double i1 = 100.0;
    Double i2 = 100.0;
    Double i3 = 200.0;
    Double i4 = 200.0;

    System.out.println(i1==i2);     // false
    System.out.println(i3==i4);     // false
}

​ 那是因为 Double 这个包装类并没有缓存池的概念,也就是说它会为每一个 double 型数值包装一个新的 Double 实例。正如它的 valueOf 方法:

public static Double valueOf(double d) { 
  return new Double(d);
 } 

class字节码

public static void main(String[] args) {
        Integer i1 = Integer.valueOf(400);//声明Integer变量并赋值,其实是调用了valueOf方法
        Integer i2 = Integer.valueOf(400);
        Integer i3 = Integer.valueOf(0);
        Integer i4 = new Integer(400);
        Integer i5 = new Integer(400);
        Integer i6 = new Integer(0);
        System.out.println("i1 == i2," + (i1 == i2));
        System.out.println("i1 == (i2+i3)," + (i1.intValue() == i2.intValue() + i3.intValue()));
        System.out.println("i1 == i4," + (i1 == i4));
        System.out.println("i4 == i5," + (i4 == i5));
        System.out.println("i4 == (i5+i6)," + (i4.intValue() == i5.intValue() + i6.intValue()));
    }

valueOf()

public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
        //在这个范围内的数字则从常量池IntegerCache中获取,返回引用
            return IntegerCache.cache[i + (-IntegerCache.low)];
        //超过范围则返回新建的对象引用
        return new Integer(i);
}
public static  void main(String args[]){
        Integer i1 = 400;
        Integer i2 = 400;
        System.out.println("i1 == i2,"+(i1==i2));
        //400不在常量池中,此时是valueOf方法内的 return  new Integer(i),返回新建对象
}

总结

​ 由上面的代码可得知,用Integer来声明变量时,会调用其内部的valueOf()方法,而此时valueOf()内部是会判断赋值的数值是否在Integer的IntegerCache(内部类)的cache中,假若有则直接返回IntegerCache中对应cache的引用即可,否则要创建Integer对象,并返回引用。

其他包装类

包装类缓存池类型缓存池内容
IntegerIntegerCache-128~127
ByteByteCache-128~127 (此区间的数字转换成字节,其他包装类也是转成自己的类型)
LongLongCache-128~127
ShortShortCache-128~127
CharacterCharacterCache0~127
Booleantrue , falsetrue , false

自动拆装箱

​ 所谓「拆箱」就是指,包装类型转换为基本类型的过程,而所谓的「装箱」则是基本类型到包装类型的过程。例如:

public static void main(String[] args){
    int age = 21;
    Integer integer = new Integer(age);    //装箱
    int num = integer.intValue();          //拆箱
}

自从 jdk1.5 以后,引入了自动拆装箱的概念,上述代码可以简化成如下代码:

public static void main(String[] args){
    int age = 21;
    Integer integer = age;              //自动装箱
    int num = integer;                  //自动拆箱
}

是不是感觉简便了很多,但是实际上在 JVM 层面是没有变化的,这都是编译器做的「假象」。

image

​ 只是编译器允许你这样书写代码了,其实编译成字节码指令的时候,编译器还是会调用相应的拆装箱方法的。

​ 可以看到,拆装箱是需要方法调用的,也就是需要栈帧的入栈出栈的,直白点说,就是耗资源,所以我们的程序中应当尽量避免大量的「拆装箱」操作。

赞(2) 打赏
未经允许不得转载:刘鹏博客 » Java基础学习总结–基本数据类型-包装类-引用类型
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏