Java SE基础巩固(一):基本类型的包装类源码解读

Java攻城师 2020-11-10 15:10:50
java 基础 基本 se 巩固


Java中变量类型可分为两类:基本类型和引用类型。基本类型有8种,分别是short,int,long,byte,char,float,double,boolean,同时也有8种引用类型作为其包装类,例如Integer,Double等。本文要讨论的就是这些基本类型和其包装类。
《2020最新Java基础精讲视频教程和学习路线!》

下面涉及到源码的部分也仅仅列出部分有代表性的源码,不会有大段的代码。我使用的JDK版本是JDK1.8_144

1 Integer(整形)

Integer是基本类型int的包装类型。下图是其继承体系:

[](https://imgchr.com/i/ilIpdA)

[ilIpdA.png

](https://imgchr.com/i/ilIpdA)

Integer继承了Number类,实现了Comparable即可,拥有可比较的能力,还间接的实现了Serializable接口,即是可序列化的。实际其他几个数字相关的包装类型都是这么一个继承体系,所以如果下文中没有特殊说明,就表示继承体系和Integer一样。

先来看看valueOf()方法,在Integer类里有三个重载的valueOf()方法,但最终都会调用public static Integer valueOf(int i)。下面是public static Integer valueOf(int i)的源码:

 public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
复制代码

可以看到,该方法接受一个基本类型int参数,并返回一个Integer对象,需要注意的是那个if判断,这里会判断这个i是否在[IntegerCache.low,IntegerCache.high]区间里,如果在就直接返回缓存值,那这个low和hight的值是多少呢?我们到IntegerCache类里看看:

 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() {}
}
复制代码

这是Integer的一个内部静态类,从代码中,不难看出low的值是-128,high的值默认是127,其中high值可以通过java.lang.Integer.IntegerCache.high属性来设置,这个类的逻辑代码几乎都在static块里,也就是说在类加载的时候会被执行了,执行的结果是什么呢?关注这几行代码:

 cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
复制代码

将区间在[low,hight]的值都构造成Integer对象,并保存在cache数组里。这时候再回头看看valueOf()方法,发现如果参数i的值在[low,hight]区间里,那么就会返回cache里对应的Integer对象,话句话说,如果参数在这个区间范围里,那么调用该方法的时候就不需要创建对象了,直接使用缓存里的对象,减少了创建对象的开销。

那为什么要搞这么复杂呢?因为Java5提供了自动装箱和自动拆箱的语法糖,而这个方法其实就是为自动装箱服务的(从注释可以看到,这个方法从java5才开始有的,由此可推断应该和自动装箱拆箱机制有关),Integer是一个非常常用的类,有了这个缓存机制,就不需要每次在自动装箱或者手动调用的时候创建对象了,大大提高了对象复用率,也提供了运行效率。[-128,127]是一个比较保守的范围,用户可以通过修改java.lang.Integer.IntegerCache.high来修改high值,但最好不要太大,因为毕竟Intger对象也是要占用不少内存的,如果上界设置的太大,而大的值又不经常使用,那么这个缓存就有些得不偿失了。

再来看看另一个方法intValue(),这个方法就非常简单了:

 public int intValue() {
return value;
}
复制代码

value是Integer类的私有字段,类型是int,就是代表了实际的值,该方法实际上就是自动拆箱的时候调用的方法。

其他方法就没什么可说的了,大多数是一些功能性的方法(例如max,min,toBinaryString)和一些常量(例如MAX_VALUE,MIN_VALUE),感兴趣的朋友可以自行查看,算法逻辑也都不难。

其他几个就不多说了,在理解了Integer的源码之后,其他的都不难看懂。

2 自动装箱和自动拆箱

自动装箱和自动拆箱是Java5提供的语法糖,当我们需要在基本类型和包装类之间进行比较、赋值等操作的时候,编译器会帮我们自动进行类型转换,装箱就是将基本类型转换成包装类型,拆箱就是与之相反的一个过程。例如,我们有下面这样的代码:

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
int a = 1, b = 2, c = 3;
list.add(a);
list.add(b);
list.add(c);
}
复制代码

编译后的.class文件内容如下(做了转码的,实际上.class文件应该是字节码,但为了方便查看,就将其转成了java代码的形式):

public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
byte var2 = 1;
byte var3 = 2;
byte var4 = 3;
var1.add(Integer.valueOf(var2));
var1.add(Integer.valueOf(var3));
var1.add(Integer.valueOf(var4));
}
复制代码

这里就发生了装箱操作,编译器帮我们调用了Integer.valueOf()方法将int类型转换了Integer类型以适应List容器(容器的类型参数不能是基本类型)。

拆箱也类似,只是方向和装箱相反而已,一般发生在将包装类型的值赋值给基本类型时候,例如:

Integer a = 1;
int b = a; //自动拆箱
复制代码

如果没有自动拆箱,上面的代码肯定是无法编译通过,因为类型不匹配,又没有进行强转。

关于自动装箱和拆箱更多的内容,网上有很多资料(非常非常多),建议有兴趣的朋友可以到网上搜索搜索。

3 选择基本类型还是包装类型呢?

首先,我们先明确一个基本原则:如果某个场景中不能使用基本类型和包装类型的其中一种,那么就只能选择另外一种。例如,容器的泛型类型参数不能是基本类型,那就不要有什么犹豫了,直接使用包装类型吧。

当两种类型都可用的时候,建议使用基本类型,主要原因有如下3个:

  1. 包装类型是一个类,要使用某个值就必须要创建对象,即使有缓存的情况下可以节省创建对象的时间开销,但空间开销仍然存在,一个对象包含了对象头,实例数据和对象填充,但实际上我们仅仅需要实例数据而已,对于Integer来说,我们仅仅需要value字段的值而已,也就是说使用基本类型int只占用了4个字节的空间,而使用对象需要几倍于基本类型的空间(大家可以计算一下)。
  2. 接着第1条,对象是有可能为null的,这样在使用的时候有时候不得不对其进行空值判断,少数几个还好,当业务逻辑复杂的时候(或者程序员写代码写上头的时候),就非常容易遗漏,一旦遗漏就很有可能发生空指针异常,如果没有严密的测试,还真不一定能测试出来。
  3. 包装类在进行比较操作的时候有时候会让人迷惑,我们举个例子,假设有如下代码:

     public static void main(String[] args) {
    Integer d = 220;
    Integer e = 220;
    int f = 220;
    System.out.println(d == e);
    System.out.println(e == f);
    }
    复制代码

    如果没有遇到过这样的问题,可能下意识的说出执行的结果是true和true,但实际上,输出的结果是false,true。为什么?对于第一个比较,d和e都是Integer对象,直接用==比较的实际上引用的值,而不是实际的值,在将220赋值给她们的时候实际上发生过自动装箱,即调用过valueOf()方法,而220超出了默认的缓存区间,那么就会创建新的Interge对象,这两个a和b的引用肯定就不相等,所以会输出false。第二个比较中,f是基本类型,在这里情况下,会发生自动拆箱,==两边的变量实际上基本类型int,直接比较时没有问题的,所以输出结果会是true。

总之,如果能用基本类型,最好还是使用基本类型,而不是其包装类,如果实在无法使用基本类型,例如容器的泛型类型参数,才使用包装类。

4 小结

本文介绍了Integre包装类,对其源码进行了简单分析,之所以没有对其他基本类型的保证类进行分析是因为他们的实现非常相似,重复介绍就没有意思了。还简单讨论了一下自动装箱和自动拆箱,这个特性方便了程序员,提高了开发效率,但如果使用不当可能会造成令人迷惑的问题,算是有利有弊吧。对于在基本类型和包装类型之间的选择,我个人倾向于优先使用基本类型,原因也在文中有比较详细的解释。

版权声明
本文为[Java攻城师]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000037786321

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云