关于 “Java基本数据类型和它的包装类“ 我想说......

导语:也许大家会感到好奇怪 为什么Java是一门面向对象的程序设计语言 为什么要引入基本数据类型呢?这显然与面向对象有悖 我认为原因可能如下:
Ⅰ.Java是C++语言演变而来 在这之前很大一部分程序员已经习惯了微软系的C/C++语言中的基本数据类型 为了让C++/C转Java的程序员能更快适应和接受Java语言 Java引入了基本数据类型尽管这与面向对象有悖(基本数据类型广泛存在于面向过程语言中)
Ⅱ.基本数据类型 相比于类更加轻量 而且大小也是比较固定 易维护易存储

一.基本数据类型为什么要存储在Java栈中而不是堆中?

Ⅰ.这个问题我们还需回归基本数据类型本身来进行分析 我们都知道基本数据类型大小固定 而恰恰Java栈是一个个栈帧组成 并且每一个栈帧的大小也是固定的 这好像非常适合用来存放基本数据类型
Ⅱ.我们都知道 栈相比于堆还有一个优势便是 栈设计简单 所以栈的运行速度肯定比堆快 所以Java没有理由不选择栈用来存放基本数据类型

二.既然都有了基本数据类型 为什么又要设计他们的包装类型?

我们先解释一下什么叫 基本数据类型的包装类型 Java是一门面向对象的编程语言 所以Java本着一切皆为对象的原则(事实上确实Java世界里一切皆为对象)所以SUN开发团队为每一种基本数据类型都定制了一个专属包装类 例如float的包装类为Float long的包装类为Long 规律大概就是 基本数据类型首字母大写即为它的包装类 但是int例外 int 的包装类为Integer 这与它的语义和Java预留关键字有关 这边不过多解释 而且这完全不影响我们使用它们

三.包装类与基本数据类型相互转化 拆箱与装箱

3.1. 装箱(基本数据类型 ->包装类)

自动装箱 就是Java会自动将基本数据类型自动装箱转化为对应的包装类
这一操作不用程序员去转换所以叫自动装箱

public class Test {
    public static void main(String[] args) {
        Integer num=1;
    }
}

上述代码就是自动装箱 我们居然把一个 基本数据类型值 1 赋给了一个 引用类型变量 这一过程其实本质上是由Java语法糖实现的自动装箱(由于JVM本身不能理解语法糖 所以看过JVM源码都知道 JVM底层需要调用desuger()进行解语法糖)关于Java语法糖大家可以自行上某度或Google学习 或移步我的另外一篇博客 Java语法糖
我们看问题不能看表象 要知其然还要知其所以然 本着这一原则我们去看看JVM底层到底是怎么实现的自动装箱 我们找到它的 .class文件反编译一下看看
在这里插入图片描述
反编译之后代码
在这里插入图片描述
原来如此 它底层其实是通过调用 Integer.valueOf方法进行自动装箱的 其他的包装类我想也是如此 Integer.valueOf()方法其实就是将数值包装成一个Integer对象然后返回给调用者 ,规律大概就是 xxx.valueOf() 就不一一举例了
需要注意这一步我们通过反编译程序看到的代码已经是JVM解语法糖之后的代码了

3.2. 拆箱(包装类 ->基本数据类型)

自动拆箱 顾名思义就是Java会自动拆开"包装类"将其转化为对应的基本数据类型值 这一操作不用程序员去转换所以叫自动拆箱

public class Test {
    public static void main(String[] args) {

        //这一步里面蕴含自动装箱 因为你居然拿一个数字赋值给了一个类
        Integer num=1;
        
        //这一步里面蕴含自动拆箱 因为你居然拿一个类和一个数字相加没有报错
        int sun=num+1;
    }
}

老规矩 看反编译结果
在这里插入图片描述
原来如此 它底层其实是通过调用 实例.intValue()方法进行自动拆箱的 intValue()方法其实就是返回Integer对象的int字面量值 然后返回给调用者 其他的包装类我想也是如此 规律大概就是 实例.xxxValue() 就不一一举例了 需要注意这一步我们通过反编译程序看到的代码已经是JVM解语法糖之后的代码了

四.包装类的常量池技术

首先明确一点 我们这里说的常量池其实是运行时常量池 因为Java常量池其实分为静态常量池动态常量池的 静态常量池的代表便是 class常量池 动态常量池有很多 我们今天要讲的包装类常量池就是其中之一

4.1.为什么要引入包装类常量池

因为SUN开发团队认为有一个范围的数值经常会被使用 如果每次都进行新的包装类对象创建未免有点得不偿失 所以他们引入了常量池 实现对象复用。对于包装类常量池我们可以类比 字符串常量池 其实用意和思想完全一致 但是字符串常量池会对所有字符串做常量池缓存 但是包装类常量池仅仅只对一个范围内的数值做常量池缓存 例如Integer的常量池缓存范围为 -128~127 事实上所有的数值型常量池(所有的包装类都有常量池吗?我下面会有解读)缓存范围都是-128 ~127 你没发现这刚好是一个byte吗?
知道这个有什么用呢?我们看一个例子

public class Test {
    public static void main(String[] args) {

        Integer num1 = 127;
        Integer num2 = 127;

        Integer num3 = 128;
        Integer num4 = 128;

        Integer num5 = 6;
        Integer num6 = 6;

        System.out.println(num1 == num2);

        System.out.println(num3 == num4);

        System.out.println(num5 == num6);
    }
}

答案会是什么呢?这就与常量池息息相关了 当数值范围在 -128~127范围内 它与字符串常量池一样 都是从常量池中获取对象 所以字面量相等的两个对象 用 == 判断 返回 Ture 因为 == 判断的是两个对象的内存地址 字面量相等那这两个数值在常量池中的地址就一样 所以返回true。

首先 上面我说过Java语法糖会在编译期对 Integer num1=127进行自动装箱操作 变为 Integer num1=Integer.valueOf(127) 所以要了解底层原理 我们需要对Integer的 public static Integer valueOf(int i) 这个方法进行分析与探究

/**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

从上面Integer的源码我们就可以知道 IntegerCache.low=-128 IntegerCache.high=127 当在这个范围内时从IntegerCache这个常量池缓存中获取值 否则new 一个Integer对象 我们都知道new 出来的对象用 = =比较时他只和自己相等 所以上面试题的num3 == num4应该返回false 我们看一下运行结果在这里插入图片描述

4.2 所有的包装类都有常量池吗?

有些包装类根本就没法去做常量池缓存 那就是 带小数点的数值 因为他们太稠密了 你根本无法缓存(你能告诉我1.01~2.01一共有多少个数吗?学过数论的都知道 答案是无数个 既然是无数那我的常量池是不是要缓存无数个对象?抛开内存不是无限大不说 这简直就是脱裤子放屁 何不直接创建对象) 再者本身float 和double也是近似存储的,所以注意FloatDouble是没有常量池技术的 以后也不会有 因为没必要 也实现不了对象复用

五.关于包装类存在的价值和使用场景

5.1.需要使用包装类场景

比如说 我们现在有一个业务需要对学生的考试成绩进行记录 那么我们首先想到使用 float字段去保存学生成绩 但是现在就有一个业务我们就很难区分 因为 考0分和缺考其实是两种状态 不管你用int 还是float他们都有默认值 比如int是0 那0到底是缺考还是考了0分呢?所以这个时候使用包装类就显得尤为合适 因为包装类是类 类如果没有进行初始化 那他就是null null就说明学生缺考 有值就是参加了考试 如果值为0那就是考了0分 所以就很自然的解决了上诉问题

5.2.什么场景下我们不该盲目使用包装类

我们都知道 包装类存在自动装箱和拆箱 所以频繁拆箱和装箱其实是很消耗性能的
我举一个例子大家就清楚了 例子比较极端却很能说明问题在这里插入图片描述

在这里插入图片描述
你看看 仅仅只是一个变量定义之差 性能相差15倍之多 因为上面你把num定义成了Long 对Long进行++操作 就会频繁进行装箱与拆箱 对程序性能影响特别大特别大 下面程序只耗时0.8秒 而上面的程序耗时将近10秒

总结:包装类与基本数据类型都有各自的应用范围 都有其存在的价值 为了更好的写出高质量程序 我们必须了解和使用包装类和基本数据类型 文章不能面面俱到 希望能帮助你更好的理解和学习Java 关注我带你解锁更多Java新姿势

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值